<?php

declare(strict_types=1);

namespace App\Services\NovaCards;

use App\Models\NovaCard;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;

/**
 * Computes and returns related cards for a given card using multiple
 * similarity signals: template family, category, mood tags, format,
 * style family, palette family, and creator.
 */
class NovaCardRelatedCardsService
{
    private const CACHE_TTL = 600;

    private const LIMIT = 8;

    public function related(NovaCard $card, int $limit = self::LIMIT, bool $cached = true): Collection
    {
        if ($cached) {
            return Cache::remember(
                'nova_cards.related.' . $card->id . '.' . $limit,
                self::CACHE_TTL,
                fn () => $this->compute($card, $limit),
            );
        }

        return $this->compute($card, $limit);
    }

    public function invalidateForCard(NovaCard $card): void
    {
        foreach ([4, 6, 8, 12] as $limit) {
            Cache::forget('nova_cards.related.' . $card->id . '.' . $limit);
        }
    }

    private function compute(NovaCard $card, int $limit): Collection
    {
        $card->loadMissing(['tags', 'category', 'template']);

        $tagIds = $card->tags->pluck('id')->all();
        $templateId = $card->template_id;
        $categoryId = $card->category_id;
        $format = $card->format;
        $styleFamily = $card->style_family;
        $paletteFamily = $card->palette_family;
        $creatorId = $card->user_id;

        $query = NovaCard::query()
            ->publiclyVisible()
            ->where('nova_cards.id', '!=', $card->id)
            ->select(['nova_cards.*'])
            ->selectRaw('0 AS relevance_score');

        // We build a union-ranked set via scored sub-queries, then re-aggregate
        // in PHP (simpler than scoring in MySQL without a full-text index).
        $candidates = NovaCard::query()
            ->publiclyVisible()
            ->where('nova_cards.id', '!=', $card->id)
            ->where(function ($q) use ($tagIds, $templateId, $categoryId, $format, $styleFamily, $paletteFamily, $creatorId): void {
                $q->whereHas('tags', fn ($tq) => $tq->whereIn('nova_card_tags.id', $tagIds))
                  ->orWhere('template_id', $templateId)
                  ->orWhere('category_id', $categoryId)
                  ->orWhere('format', $format)
                  ->orWhere('style_family', $styleFamily)
                  ->orWhere('palette_family', $paletteFamily)
                  ->orWhere('user_id', $creatorId);
            })
            ->with(['user.profile', 'category', 'template', 'backgroundImage', 'tags'])
            ->limit(80)
            ->get();

        // Score in PHP — lightweight for this candidate set size.
        $scored = $candidates->map(function (NovaCard $c) use ($tagIds, $templateId, $categoryId, $format, $styleFamily, $paletteFamily, $creatorId): array {
            $score = 0;

            // Tag overlap: up to 10 points
            $overlap = count(array_intersect($c->tags->pluck('id')->all(), $tagIds));
            $score += min($overlap * 2, 10);

            // Same template: 5 pts
            if ($templateId && $c->template_id === $templateId) {
                $score += 5;
            }

            // Same category: 3 pts
            if ($categoryId && $c->category_id === $categoryId) {
                $score += 3;
            }

            // Same format: 2 pts
            if ($c->format === $format) {
                $score += 2;
            }

            // Same style family: 4 pts
            if ($styleFamily && $c->style_family === $styleFamily) {
                $score += 4;
            }

            // Same palette: 3 pts
            if ($paletteFamily && $c->palette_family === $paletteFamily) {
                $score += 3;
            }

            // Same creator (more cards by creator): 1 pt
            if ($c->user_id === $creatorId) {
                $score += 1;
            }

            // Engagement quality boost (saves + remixes weighted)
            $engagementBoost = min(($c->saves_count + $c->remixes_count * 2) * 0.1, 3.0);
            $score += $engagementBoost;

            return ['card' => $c, 'score' => $score];
        });

        return $scored
            ->sortByDesc('score')
            ->take($limit)
            ->pluck('card')
            ->values();
    }
}
