<?php

declare(strict_types=1);

namespace App\Http\Controllers\User;

use App\Http\Controllers\Controller;
use App\Http\Requests\Collections\CollectionSavedLibraryRequest;
use App\Models\Collection;
use App\Models\CollectionSavedList;
use App\Services\CollectionRecommendationService;
use App\Services\CollectionSavedLibraryService;
use App\Services\CollectionService;
use Inertia\Inertia;
use Inertia\Response;

class SavedCollectionController extends Controller
{
    public function __construct(
        private readonly CollectionService $collections,
        private readonly CollectionSavedLibraryService $savedLibrary,
        private readonly CollectionRecommendationService $recommendations,
    ) {
    }

    public function index(CollectionSavedLibraryRequest $request): Response
    {
        return $this->renderSavedLibrary($request);
    }

    public function showList(CollectionSavedLibraryRequest $request, string $listSlug): Response
    {
        $list = $this->savedLibrary->findListBySlugForUser($request->user(), $listSlug);

        return $this->renderSavedLibrary($request, $list);
    }

    private function renderSavedLibrary(CollectionSavedLibraryRequest $request, ?CollectionSavedList $activeList = null): Response
    {
        $savedCollections = $this->collections->getSavedCollectionsForUser($request->user(), 120);
        $filter = (string) ($request->validated('filter') ?? 'all');
        $sort = (string) ($request->validated('sort') ?? 'saved_desc');
        $query = trim((string) ($request->validated('q') ?? ''));
        $listId = $activeList ? (int) $activeList->id : ($request->filled('list') ? (int) $request->query('list') : null);
        $preserveListOrder = false;
        $listOrder = null;

        if ($activeList) {
            $preserveListOrder = true;
            $allowedCollectionIds = $this->savedLibrary->collectionIdsForList($request->user(), $activeList);
            $listOrder = array_flip($allowedCollectionIds);
            $savedCollections = $savedCollections
                ->filter(fn ($collection) => in_array((int) $collection->id, $allowedCollectionIds, true))
                ->sortBy(fn ($collection) => $listOrder[(int) $collection->id] ?? PHP_INT_MAX)
                ->values();
        } elseif ($listId) {
            $preserveListOrder = true;
            $activeList = $request->user()->savedCollectionLists()->withCount('items')->findOrFail($listId);
            $allowedCollectionIds = $this->savedLibrary->collectionIdsForList($request->user(), $activeList);
            $listOrder = array_flip($allowedCollectionIds);
            $savedCollections = $savedCollections
                ->filter(fn ($collection) => in_array((int) $collection->id, $allowedCollectionIds, true))
                ->sortBy(fn ($collection) => $listOrder[(int) $collection->id] ?? PHP_INT_MAX)
                ->values();
        }

        $savedCollectionIds = $savedCollections->pluck('id')->map(static fn ($id): int => (int) $id)->all();
        $notes = $this->savedLibrary->notesFor($request->user(), $savedCollectionIds);
        $saveMetadata = $this->savedLibrary->saveMetadataFor($request->user(), $savedCollectionIds);
        $filterCounts = $this->filterCounts($savedCollections, $notes);

        $savedCollections = $savedCollections
            ->filter(fn (Collection $collection): bool => $this->matchesSearch($collection, $query))
            ->filter(fn (Collection $collection): bool => $this->matchesFilter($collection, $filter, $notes))
            ->values();

        if (! ($preserveListOrder && $sort === 'saved_desc')) {
            $savedCollections = $this->sortCollections($savedCollections, $sort)->values();
        }

        $collectionPayloads = $this->collections->mapCollectionCardPayloads($savedCollections, false);
        $collectionIds = collect($collectionPayloads)->pluck('id')->map(static fn ($id) => (int) $id)->all();
        $memberships = $this->savedLibrary->membershipsFor($request->user(), $collectionIds);
        $savedLists = collect($this->savedLibrary->listsFor($request->user()))
            ->map(function (array $list) use ($filter, $sort, $query): array {
                return [
                    ...$list,
                    'url' => route('me.saved.collections.lists.show', ['listSlug' => $list['slug']]) . ($filter !== 'all' || $sort !== 'saved_desc'
                        ? ('?' . http_build_query(array_filter([
                            'filter' => $filter !== 'all' ? $filter : null,
                            'sort' => $sort !== 'saved_desc' ? $sort : null,
                            'q' => $query !== '' ? $query : null,
                        ])))
                        : ''),
                ];
            })
            ->values()
            ->all();

        $filterOptions = [
            ['key' => 'all', 'label' => 'All', 'count' => $filterCounts['all'] ?? 0],
            ['key' => 'editorial', 'label' => 'Editorial', 'count' => $filterCounts['editorial'] ?? 0],
            ['key' => 'community', 'label' => 'Community', 'count' => $filterCounts['community'] ?? 0],
            ['key' => 'personal', 'label' => 'Personal', 'count' => $filterCounts['personal'] ?? 0],
            ['key' => 'seasonal', 'label' => 'Seasonal or campaign', 'count' => $filterCounts['seasonal'] ?? 0],
            ['key' => 'noted', 'label' => 'With notes', 'count' => $filterCounts['noted'] ?? 0],
            ['key' => 'revisited', 'label' => 'Revisited', 'count' => $filterCounts['revisited'] ?? 0],
        ];

        $sortOptions = [
            ['key' => 'saved_desc', 'label' => 'Recently saved'],
            ['key' => 'saved_asc', 'label' => 'Oldest saved'],
            ['key' => 'updated_desc', 'label' => 'Recently updated'],
            ['key' => 'revisited_desc', 'label' => 'Recently revisited'],
            ['key' => 'ranking_desc', 'label' => 'Highest ranking'],
            ['key' => 'title_asc', 'label' => 'Title A-Z'],
        ];

        return Inertia::render('Collection/SavedCollections', [
            'collections' => collect($collectionPayloads)->map(function (array $collection) use ($memberships, $notes, $saveMetadata): array {
                return [
                    ...$collection,
                    'saved_list_ids' => $memberships[(int) $collection['id']] ?? [],
                    'saved_note' => $notes[(int) $collection['id']] ?? null,
                    'saved_because' => $saveMetadata[(int) $collection['id']]['saved_because'] ?? null,
                    'last_viewed_at' => $saveMetadata[(int) $collection['id']]['last_viewed_at'] ?? null,
                ];
            })->all(),
            'recentlyRevisited' => $this->collections->mapCollectionCardPayloads($this->savedLibrary->recentlyRevisited($request->user(), 6), false),
            'recommendedCollections' => $this->collections->mapCollectionCardPayloads($this->recommendations->recommendedForUser($request->user(), 6), false),
            'savedLists' => $savedLists,
            'activeList' => $activeList ? [
                'id' => (int) $activeList->id,
                'title' => (string) $activeList->title,
                'slug' => (string) $activeList->slug,
                'items_count' => (int) $activeList->items_count,
                'url' => route('me.saved.collections.lists.show', ['listSlug' => $activeList->slug]),
            ] : null,
            'activeFilters' => [
                'q' => $query,
                'filter' => $filter,
                'sort' => $sort,
                'list' => $listId,
            ],
            'filterOptions' => $filterOptions,
            'sortOptions' => $sortOptions,
            'endpoints' => [
                'createList' => route('me.saved.collections.lists.store'),
                'addToListPattern' => route('me.saved.collections.lists.items.store', ['collection' => '__COLLECTION__']),
                'removeFromListPattern' => route('me.saved.collections.lists.items.destroy', ['list' => '__LIST__', 'collection' => '__COLLECTION__']),
                'reorderItemsPattern' => route('me.saved.collections.lists.items.reorder', ['list' => '__LIST__']),
                'updateNotePattern' => route('me.saved.collections.notes.update', ['collection' => '__COLLECTION__']),
                'unsavePattern' => route('collections.unsave', ['collection' => '__COLLECTION__']),
            ],
            'libraryUrl' => route('me.saved.collections'),
            'browseUrl' => route('collections.featured'),
            'seo' => [
                'title' => $activeList ? sprintf('%s — Saved Collections — Skinbase', $activeList->title) : 'Saved Collections — Skinbase',
                'description' => $activeList ? sprintf('Saved collections in the %s list on Skinbase.', $activeList->title) : 'Your saved collections on Skinbase.',
                'canonical' => $activeList ? route('me.saved.collections.lists.show', ['listSlug' => $activeList->slug]) : route('me.saved.collections'),
                'robots' => 'noindex,follow',
            ],
        ])->rootView('collections');
    }

    private function matchesSearch(Collection $collection, string $query): bool
    {
        if ($query === '') {
            return true;
        }

        $haystacks = [
            $collection->title,
            $collection->subtitle,
            $collection->summary,
            $collection->description,
            $collection->campaign_label,
            $collection->season_key,
            $collection->event_label,
            $collection->series_title,
            optional($collection->user)->username,
            optional($collection->user)->name,
        ];

        $needle = mb_strtolower($query);

        return collect($haystacks)
            ->filter(fn ($value): bool => is_string($value) && $value !== '')
            ->contains(fn (string $value): bool => str_contains(mb_strtolower($value), $needle));
    }

    /**
     * @param array<int, string> $notes
     */
    private function matchesFilter(Collection $collection, string $filter, array $notes): bool
    {
        return match ($filter) {
            'editorial' => $collection->type === Collection::TYPE_EDITORIAL,
            'community' => $collection->type === Collection::TYPE_COMMUNITY,
            'personal' => $collection->type === Collection::TYPE_PERSONAL,
            'seasonal' => filled($collection->season_key) || filled($collection->event_key) || filled($collection->campaign_key),
            'noted' => filled($notes[(int) $collection->id] ?? null),
            'revisited' => $this->timestamp($collection->saved_last_viewed_at) !== $this->timestamp($collection->saved_at),
            default => true,
        };
    }

    /**
     * @param array<int, string> $notes
     * @return array<string, int>
     */
    private function filterCounts($collections, array $notes): array
    {
        return [
            'all' => $collections->count(),
            'editorial' => $collections->where('type', Collection::TYPE_EDITORIAL)->count(),
            'community' => $collections->where('type', Collection::TYPE_COMMUNITY)->count(),
            'personal' => $collections->where('type', Collection::TYPE_PERSONAL)->count(),
            'seasonal' => $collections->filter(fn (Collection $collection): bool => filled($collection->season_key) || filled($collection->event_key) || filled($collection->campaign_key))->count(),
            'noted' => $collections->filter(fn (Collection $collection): bool => filled($notes[(int) $collection->id] ?? null))->count(),
            'revisited' => $collections->filter(fn (Collection $collection): bool => $this->timestamp($collection->saved_last_viewed_at) !== $this->timestamp($collection->saved_at))->count(),
        ];
    }

    private function sortCollections($collections, string $sort)
    {
        return match ($sort) {
            'saved_asc' => $collections->sortBy(fn (Collection $collection): int => $this->timestamp($collection->saved_at)),
            'updated_desc' => $collections->sortByDesc(fn (Collection $collection): int => $this->timestamp($collection->updated_at)),
            'revisited_desc' => $collections->sortByDesc(fn (Collection $collection): int => $this->timestamp($collection->saved_last_viewed_at)),
            'ranking_desc' => $collections->sortByDesc(fn (Collection $collection): float => (float) ($collection->ranking_score ?? 0)),
            'title_asc' => $collections->sortBy(fn (Collection $collection): string => mb_strtolower((string) $collection->title)),
            default => $collections->sortByDesc(fn (Collection $collection): int => $this->timestamp($collection->saved_at)),
        };
    }

    private function timestamp(mixed $value): int
    {
        if ($value instanceof \DateTimeInterface) {
            return $value->getTimestamp();
        }

        if (is_numeric($value)) {
            return (int) $value;
        }

        if (is_string($value) && $value !== '') {
            $timestamp = strtotime($value);

            return $timestamp !== false ? $timestamp : 0;
        }

        return 0;
    }
}
