<?php

namespace MintyPHP\Service\Search;

use MintyPHP\Repository\Search\SearchQueryRepository;
use MintyPHP\Repository\Tenant\UserTenantRepository;
use MintyPHP\Service\Access\PermissionService;
use MintyPHP\Support\SearchConfig;

class SearchDataService
{
    public function __construct(
        private readonly PermissionService $permissionService,
        private readonly UserTenantRepository $userTenantRepository,
        private readonly SearchQueryRepository $searchQueryRepository
    ) {
    }

    /**
     * @return array{data: array<int, array<string, mixed>>, total: int}
     */
    public function buildResultData(int $userId, string $query, string $locale, int $limit, int $offset): array
    {
        $tenantScope = $this->resolveTenantScope($userId);
        $tenantIds = $tenantScope['tenantIds'];
        $tenantScoped = $tenantScope['tenantScoped'];

        $tenantScopeFilters = SearchConfig::tenantScopeFilters();
        $resources = SearchConfig::resources($query, $locale);
        $items = [];

        foreach ($resources as $resource) {
            $key = (string) ($resource['key'] ?? '');
            if ($key === '') {
                continue;
            }

            $perm = (string) ($resource['permission'] ?? '');
            if ($perm !== '' && !$this->permissionService->userHas($userId, $perm)) {
                continue;
            }

            $tenantFilter = (string) ($tenantScopeFilters[$key] ?? '');
            $isTenantScoped = $tenantFilter !== '';
            if ($isTenantScoped && !$tenantScoped) {
                continue;
            }

            $sql = (string) ($resource['resultSql'] ?? '');
            if ($sql === '') {
                continue;
            }
            $sql = str_replace('{{tenantFilter}}', $isTenantScoped ? $tenantFilter : '', $sql);
            $params = is_array($resource['resultParams'] ?? null) ? $resource['resultParams'] : [];
            if ($isTenantScoped && strpos($sql, '???') !== false) {
                $params[] = $tenantIds;
            }

            $rows = $this->searchQueryRepository->selectRows($sql, $params);
            foreach ($rows as $row) {
                $data = self::normalizeResourceRow($row);
                $mapped = SearchConfig::mapResultItem($key, (string) ($resource['label'] ?? ''), $data);
                if ($mapped !== null) {
                    $items[] = $mapped;
                }
            }
        }

        $items = array_merge($items, SearchConfig::hotkeyResultItems($query));
        if ($this->permissionService->userHas($userId, PermissionService::DOCS_VIEW)) {
            $items = array_merge($items, SearchConfig::docsResultItems($query));
        }

        $scoreQuery = SearchConfig::normalizeScoreQuery($query);
        $queryLower = mb_strtolower($scoreQuery);
        $scoreItem = static function (array $item) use ($queryLower): int {
            $manualScore = (int) ($item['search_score'] ?? 0);
            if ($manualScore > 0) {
                return $manualScore;
            }

            $title = mb_strtolower((string) ($item['title'] ?? ''));
            $description = mb_strtolower((string) ($item['description'] ?? ''));
            if ($title === $queryLower) {
                return 400;
            }
            if ($title !== '' && strpos($title, $queryLower) === 0) {
                return 300;
            }
            if ($title !== '' && strpos($title, $queryLower) !== false) {
                return 200;
            }
            if ($description !== '' && strpos($description, $queryLower) !== false) {
                return 100;
            }
            return 0;
        };

        usort($items, static function (array $a, array $b) use ($scoreItem): int {
            $scoreA = $scoreItem($a);
            $scoreB = $scoreItem($b);
            if ($scoreA !== $scoreB) {
                return $scoreB <=> $scoreA;
            }
            $title = strcmp((string) ($a['title'] ?? ''), (string) ($b['title'] ?? ''));
            if ($title !== 0) {
                return $title;
            }
            return strcmp((string) ($a['type'] ?? ''), (string) ($b['type'] ?? ''));
        });

        $total = count($items);
        $items = array_slice($items, $offset, $limit);

        return [
            'data' => $items,
            'total' => $total,
        ];
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function buildPreviewData(int $userId, string $query, string $locale, int $previewLimit = 5): array
    {
        $tenantScope = $this->resolveTenantScope($userId);
        $tenantIds = $tenantScope['tenantIds'];
        $tenantScoped = $tenantScope['tenantScoped'];

        $tenantScopeFilters = SearchConfig::tenantScopeFilters();
        $resources = SearchConfig::resources($query, $locale);
        $results = [];

        foreach ($resources as $resource) {
            $key = (string) ($resource['key'] ?? '');
            if ($key === '') {
                continue;
            }

            $tenantFilter = (string) ($tenantScopeFilters[$key] ?? '');
            $isTenantScoped = $tenantFilter !== '';
            if ($isTenantScoped && !$tenantScoped) {
                continue;
            }

            $perm = (string) ($resource['permission'] ?? '');
            if ($perm !== '' && !$this->permissionService->userHas($userId, $perm)) {
                continue;
            }

            $countSql = str_replace('{{tenantFilter}}', $isTenantScoped ? $tenantFilter : '', (string) ($resource['countSql'] ?? ''));
            $countParams = is_array($resource['countParams'] ?? null) ? $resource['countParams'] : [];
            if ($isTenantScoped && strpos($countSql, '???') !== false) {
                $countParams[] = $tenantIds;
            }
            $count = $this->searchQueryRepository->selectCount($countSql, $countParams);

            $items = [];
            $previewSql = $resource['previewSql'] ?? null;
            if (is_string($previewSql) && $previewSql !== '') {
                $previewSql = str_replace('{{tenantFilter}}', $isTenantScoped ? $tenantFilter : '', $previewSql);
                $previewParams = is_array($resource['previewParams'] ?? null) ? $resource['previewParams'] : [];
                if ($isTenantScoped && strpos($previewSql, '???') !== false) {
                    $previewParams[] = $tenantIds;
                }
                $previewParams[] = $previewLimit;
                $rows = $this->searchQueryRepository->selectRows($previewSql, $previewParams);
                foreach ($rows as $row) {
                    $data = self::normalizeResourceRow($row);
                    $mapped = SearchConfig::mapPreviewItem($key, $data, $query);
                    if ($mapped !== null) {
                        $items[] = $mapped;
                    }
                }
            }

            $result = [
                'key' => $key,
                'label' => $resource['label'] ?? '',
                'count' => $count,
                'url' => SearchConfig::listUrl($key, $query),
                'items' => $items,
            ];
            if ($key === 'pages' && !empty($items[0]['url'])) {
                $result['url'] = $items[0]['url'];
            }
            $results[] = $result;
        }

        $hotkeyResource = SearchConfig::hotkeyPreviewResource($query);
        if ($hotkeyResource !== null) {
            $results[] = $hotkeyResource;
        }

        if ($this->permissionService->userHas($userId, PermissionService::DOCS_VIEW)) {
            $docsResource = SearchConfig::docsPreviewResource($query);
            if ($docsResource !== null) {
                $results[] = $docsResource;
            }
        }

        return $results;
    }

    /**
     * @return array{tenantIds: array<int, int>, tenantScoped: bool}
     */
    private function resolveTenantScope(int $userId): array
    {
        if ($userId > 0) {
            // Prime cached permissions for the current request.
            $this->permissionService->getUserPermissions($userId);
        }

        $tenantIds = $userId > 0
            ? $this->userTenantRepository->listTenantIdsByUserId($userId)
            : [];

        return [
            'tenantIds' => $tenantIds,
            'tenantScoped' => !empty($tenantIds),
        ];
    }

    /**
     * @return array<string, mixed>
     */
    private static function normalizeResourceRow(mixed $row): array
    {
        $data = $row;
        if (is_array($row) && count($row) === 1) {
            $data = reset($row);
        }

        return is_array($data) ? $data : [];
    }
}
