<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Category;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

final class CategoryDirectoryService
{
    public function getDirectoryPayload(string $search = '', string $sort = 'popular', int $page = 1, int $perPage = 24): array
    {
        $search = trim($search);
        $sort = $this->normalizeSort($sort);
        $page = max(1, $page);
        $perPage = min(60, max(12, $perPage));

        $categories = collect(Cache::remember('categories.directory.v1', 3600, function (): array {
            $publishedArtworkScope = DB::table('artwork_category as artwork_category')
                ->join('artworks as artworks', 'artworks.id', '=', 'artwork_category.artwork_id')
                ->leftJoin('artwork_stats as artwork_stats', 'artwork_stats.artwork_id', '=', 'artworks.id')
                ->whereColumn('artwork_category.category_id', 'categories.id')
                ->where('artworks.is_public', true)
                ->where('artworks.is_approved', true)
                ->whereNull('artworks.deleted_at');

            $categories = Category::query()
                ->select([
                    'categories.id',
                    'categories.content_type_id',
                    'categories.parent_id',
                    'categories.name',
                    'categories.slug',
                ])
                ->selectSub(
                    (clone $publishedArtworkScope)->selectRaw('COUNT(DISTINCT artworks.id)'),
                    'artwork_count'
                )
                ->selectSub(
                    (clone $publishedArtworkScope)
                        ->whereNotNull('artworks.hash')
                        ->whereNotNull('artworks.thumb_ext')
                        ->orderByDesc(DB::raw('COALESCE(artwork_stats.views, 0)'))
                        ->orderByDesc(DB::raw('COALESCE(artwork_stats.favorites, 0)'))
                        ->orderByDesc(DB::raw('COALESCE(artwork_stats.downloads, 0)'))
                        ->orderByDesc(DB::raw('COALESCE(artworks.published_at, artworks.created_at)'))
                        ->orderByDesc('artworks.id')
                        ->limit(1)
                        ->select('artworks.hash'),
                    'cover_hash'
                )
                ->selectSub(
                    (clone $publishedArtworkScope)
                        ->whereNotNull('artworks.hash')
                        ->whereNotNull('artworks.thumb_ext')
                        ->orderByDesc(DB::raw('COALESCE(artwork_stats.views, 0)'))
                        ->orderByDesc(DB::raw('COALESCE(artwork_stats.favorites, 0)'))
                        ->orderByDesc(DB::raw('COALESCE(artwork_stats.downloads, 0)'))
                        ->orderByDesc(DB::raw('COALESCE(artworks.published_at, artworks.created_at)'))
                        ->orderByDesc('artworks.id')
                        ->limit(1)
                        ->select('artworks.thumb_ext'),
                    'cover_ext'
                )
                ->selectSub(
                    (clone $publishedArtworkScope)
                        ->selectRaw('COALESCE(SUM(COALESCE(artwork_stats.views, 0) + (COALESCE(artwork_stats.favorites, 0) * 3) + (COALESCE(artwork_stats.downloads, 0) * 2)), 0)'),
                    'popular_score'
                )
                ->with(['contentType:id,name,slug'])
                ->active()
                ->orderBy('categories.name')
                ->get();

            return $this->transformCategories($categories);
        }));

        $filtered = $this->filterAndSortCategories($categories, $search, $sort);
        $total = $filtered->count();
        $lastPage = max(1, (int) ceil($total / $perPage));
        $currentPage = min($page, $lastPage);
        $offset = ($currentPage - 1) * $perPage;
        $pageItems = $filtered->slice($offset, $perPage)->values();
        $popularCategories = $this->filterAndSortCategories($categories, '', 'popular')->take(4)->values();

        return [
            'data' => $pageItems->all(),
            'meta' => [
                'current_page' => $currentPage,
                'last_page' => $lastPage,
                'per_page' => $perPage,
                'total' => $total,
            ],
            'summary' => [
                'total_categories' => $categories->count(),
                'total_artworks' => $categories->sum(static fn (array $category): int => (int) ($category['artwork_count'] ?? 0)),
            ],
            'popular_categories' => $search === '' ? $popularCategories->all() : [],
            'request' => [
                'query' => $search,
                'sort' => $sort,
                'page' => $currentPage,
                'per_page' => $perPage,
            ],
        ];
    }

    private function normalizeSort(string $sort): string
    {
        return in_array($sort, ['popular', 'az', 'artworks'], true) ? $sort : 'popular';
    }

    /**
     * @param  Collection<int, array<string, mixed>>  $categories
     * @return Collection<int, array<string, mixed>>
     */
    private function filterAndSortCategories(Collection $categories, string $search, string $sort): Collection
    {
        $filtered = $categories;

        if ($search !== '') {
            $needle = mb_strtolower($search);

            $filtered = $filtered->filter(static function (array $category) use ($needle): bool {
                return str_contains(mb_strtolower((string) ($category['name'] ?? '')), $needle);
            });
        }

        return $filtered->sort(static function (array $left, array $right) use ($sort): int {
            if ($sort === 'az') {
                return strcasecmp((string) ($left['name'] ?? ''), (string) ($right['name'] ?? ''));
            }

            if ($sort === 'artworks') {
                $countCompare = ((int) ($right['artwork_count'] ?? 0)) <=> ((int) ($left['artwork_count'] ?? 0));

                return $countCompare !== 0
                    ? $countCompare
                    : strcasecmp((string) ($left['name'] ?? ''), (string) ($right['name'] ?? ''));
            }

            $scoreCompare = ((int) ($right['popular_score'] ?? 0)) <=> ((int) ($left['popular_score'] ?? 0));
            if ($scoreCompare !== 0) {
                return $scoreCompare;
            }

            $countCompare = ((int) ($right['artwork_count'] ?? 0)) <=> ((int) ($left['artwork_count'] ?? 0));
            if ($countCompare !== 0) {
                return $countCompare;
            }

            return strcasecmp((string) ($left['name'] ?? ''), (string) ($right['name'] ?? ''));
        })->values();
    }

    /**
     * @param  Collection<int, Category>  $categories
     * @return array<int, array<string, mixed>>
     */
    private function transformCategories(Collection $categories): array
    {
        $categoryMap = $categories->keyBy('id');
        $pathCache = [];

        $buildPath = function (Category $category) use (&$buildPath, &$pathCache, $categoryMap): string {
            if (isset($pathCache[$category->id])) {
                return $pathCache[$category->id];
            }

            if ($category->parent_id && $categoryMap->has($category->parent_id)) {
                $pathCache[$category->id] = $buildPath($categoryMap->get($category->parent_id)) . '/' . $category->slug;

                return $pathCache[$category->id];
            }

            $pathCache[$category->id] = $category->slug;

            return $pathCache[$category->id];
        };

        return $categories
            ->map(static function (Category $category) use ($buildPath): array {
                $contentTypeSlug = strtolower((string) ($category->contentType?->slug ?? 'categories'));
                $path = $buildPath($category);
                $coverImage = null;

                if (! empty($category->cover_hash) && ! empty($category->cover_ext)) {
                    $coverImage = ThumbnailService::fromHash((string) $category->cover_hash, (string) $category->cover_ext, 'md');
                }

                return [
                    'id' => (int) $category->id,
                    'name' => (string) $category->name,
                    'slug' => (string) $category->slug,
                    'url' => '/' . $contentTypeSlug . '/' . $path,
                    'content_type' => [
                        'name' => (string) ($category->contentType?->name ?? 'Categories'),
                        'slug' => $contentTypeSlug,
                    ],
                    'cover_image' => $coverImage ?: 'https://files.skinbase.org/default/missing_md.webp',
                    'artwork_count' => (int) ($category->artwork_count ?? 0),
                    'popular_score' => (int) ($category->popular_score ?? 0),
                ];
            })
            ->values()
            ->all();
    }
}