<?php

declare(strict_types=1);

namespace App\Services\Worlds;

use App\Enums\WorldRewardType;
use App\Models\User;
use App\Models\World;
use App\Models\WorldAnalyticsEvent;
use App\Models\WorldRewardGrant;
use App\Models\WorldSubmission;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

final class WorldAnalyticsService
{
    public const EVENT_SOURCE_IMPRESSION = 'world_source_impression';
    public const EVENT_VIEWED = 'world_viewed';
    public const EVENT_SOURCE_CLICKED = 'world_source_clicked';
    public const EVENT_CTA_CLICKED = 'world_cta_clicked';
    public const EVENT_SECTION_CLICKED = 'world_section_clicked';
    public const EVENT_ENTITY_CLICKED = 'world_entity_clicked';
    public const EVENT_SUBMISSION_STARTED = 'world_submission_started';
    public const EVENT_SUBMISSION_CREATED = 'world_submission_created';
    public const EVENT_SUBMISSION_APPROVED = 'world_submission_approved';
    public const EVENT_SUBMISSION_REMOVED = 'world_submission_removed';
    public const EVENT_SUBMISSION_BLOCKED = 'world_submission_blocked';
    public const EVENT_SUBMISSION_FEATURED = 'world_submission_featured';
    public const EVENT_CHALLENGE_CTA_CLICKED = 'world_challenge_cta_clicked';
    public const EVENT_REWARD_GRANTED = 'world_reward_granted';

    public const SOURCE_HOMEPAGE_SPOTLIGHT = 'homepage_spotlight';
    public const SOURCE_HOMEPAGE_WORLDS_RAIL = 'homepage_worlds_rail';
    public const SOURCE_WORLDS_INDEX = 'worlds_index';
    public const SOURCE_NAVIGATION = 'navigation';
    public const SOURCE_UPLOAD_FLOW = 'upload_flow';
    public const SOURCE_CHALLENGE_PAGE = 'challenge_page';
    public const SOURCE_NEWS_ARTICLE = 'news_article';
    public const SOURCE_PROFILE = 'profile';
    public const SOURCE_DIRECT = 'direct';
    public const SOURCE_UNKNOWN = 'unknown';

    private const RANGE_WINDOWS = [
        '7d' => 7,
        '30d' => 30,
        'all' => null,
    ];

    private const PORTFOLIO_RANGE_WINDOWS = [
        '30d' => 30,
        'all' => null,
    ];

    public function allowedEventTypes(): array
    {
        return [
            self::EVENT_SOURCE_IMPRESSION,
            self::EVENT_VIEWED,
            self::EVENT_SOURCE_CLICKED,
            self::EVENT_CTA_CLICKED,
            self::EVENT_SECTION_CLICKED,
            self::EVENT_ENTITY_CLICKED,
            self::EVENT_SUBMISSION_STARTED,
            self::EVENT_SUBMISSION_CREATED,
            self::EVENT_SUBMISSION_APPROVED,
            self::EVENT_SUBMISSION_REMOVED,
            self::EVENT_SUBMISSION_BLOCKED,
            self::EVENT_SUBMISSION_FEATURED,
            self::EVENT_CHALLENGE_CTA_CLICKED,
            self::EVENT_REWARD_GRANTED,
        ];
    }

    public function allowedSourceSurfaces(): array
    {
        return [
            self::SOURCE_HOMEPAGE_SPOTLIGHT,
            self::SOURCE_HOMEPAGE_WORLDS_RAIL,
            self::SOURCE_WORLDS_INDEX,
            self::SOURCE_NAVIGATION,
            self::SOURCE_UPLOAD_FLOW,
            self::SOURCE_CHALLENGE_PAGE,
            self::SOURCE_NEWS_ARTICLE,
            self::SOURCE_PROFILE,
            self::SOURCE_DIRECT,
            self::SOURCE_UNKNOWN,
        ];
    }

    public function recordEvent(Request $request, array $payload): void
    {
        $world = World::query()
            ->select(['id', 'slug', 'type', 'recurrence_key', 'edition_year'])
            ->findOrFail((int) $payload['world_id']);

        $user = $request->user();

        WorldAnalyticsEvent::query()->create([
            'world_id' => (int) $world->id,
            'event_type' => (string) $payload['event_type'],
            'world_slug' => (string) $world->slug,
            'world_type' => (string) $world->type,
            'recurrence_key' => $this->nullableString($world->recurrence_key),
            'edition_year' => $world->edition_year ? (int) $world->edition_year : null,
            'section_key' => $this->nullableString($payload['section_key'] ?? null),
            'cta_key' => $this->nullableString($payload['cta_key'] ?? null),
            'entity_type' => $this->nullableString($payload['entity_type'] ?? null),
            'entity_id' => isset($payload['entity_id']) ? (int) $payload['entity_id'] : null,
            'entity_title' => $this->nullableString($payload['entity_title'] ?? null),
            'challenge_id' => isset($payload['challenge_id']) ? (int) $payload['challenge_id'] : null,
            'source_surface' => $this->nullableString($payload['source_surface'] ?? null),
            'source_detail' => $this->nullableString($payload['source_detail'] ?? null),
            'viewer_type' => $user ? 'user' : 'guest',
            'user_id' => $user ? (int) $user->id : null,
            'visitor_key' => $this->resolveVisitorKey($request, $user, (string) ($payload['visitor_token'] ?? '')),
            'meta' => $this->sanitizeMeta($payload['meta'] ?? []),
            'occurred_at' => now(),
        ]);
    }

    public function recordSubmissionLifecycle(WorldSubmission $submission, string $eventType, ?User $actor = null, ?string $sourceSurface = null, array $meta = []): void
    {
        $submission->loadMissing(['world', 'artwork']);

        $world = $submission->world;
        if (! $world) {
            return;
        }

        WorldAnalyticsEvent::query()->create([
            'world_id' => (int) $world->id,
            'event_type' => $eventType,
            'world_slug' => (string) $world->slug,
            'world_type' => (string) $world->type,
            'recurrence_key' => $this->nullableString($world->recurrence_key),
            'edition_year' => $world->edition_year ? (int) $world->edition_year : null,
            'section_key' => 'community_submissions',
            'entity_type' => 'artwork',
            'entity_id' => $submission->artwork_id ? (int) $submission->artwork_id : null,
            'entity_title' => $this->nullableString($submission->artwork?->title),
            'source_surface' => $this->nullableString($sourceSurface),
            'viewer_type' => $actor ? 'user' : 'guest',
            'user_id' => $actor ? (int) $actor->id : null,
            'visitor_key' => $actor ? hash('sha256', 'user:' . $actor->id) : hash('sha256', 'system:world_submission'),
            'meta' => $this->sanitizeMeta(array_merge([
                'submission_id' => (int) $submission->id,
                'status' => (string) $submission->status,
                'is_featured' => (bool) $submission->is_featured,
                'mode_snapshot' => (string) ($submission->mode_snapshot ?? ''),
            ], $meta)),
            'occurred_at' => now(),
        ]);
    }

    public function recordRewardGrant(WorldRewardGrant $grant): void
    {
        $grant->loadMissing(['world', 'artwork']);

        $world = $grant->world;
        if (! $world) {
            return;
        }

        WorldAnalyticsEvent::query()->create([
            'world_id' => (int) $world->id,
            'event_type' => self::EVENT_REWARD_GRANTED,
            'world_slug' => (string) $world->slug,
            'world_type' => (string) $world->type,
            'recurrence_key' => $this->nullableString($world->recurrence_key),
            'edition_year' => $world->edition_year ? (int) $world->edition_year : null,
            'section_key' => 'rewards',
            'entity_type' => 'artwork',
            'entity_id' => $grant->artwork_id ? (int) $grant->artwork_id : null,
            'entity_title' => $this->nullableString($grant->artwork?->title),
            'viewer_type' => $grant->user_id ? 'user' : 'guest',
            'user_id' => $grant->user_id ? (int) $grant->user_id : null,
            'visitor_key' => $grant->user_id ? hash('sha256', 'user:' . $grant->user_id) : hash('sha256', 'system:world_reward'),
            'meta' => $this->sanitizeMeta([
                'reward_type' => $grant->reward_type->value,
                'grant_source' => (string) $grant->grant_source,
                'world_submission_id' => $grant->world_submission_id ? (int) $grant->world_submission_id : null,
            ]),
            'occurred_at' => now(),
        ]);
    }

    public function studioReport(World $world): array
    {
        $ranges = [];

        foreach (self::RANGE_WINDOWS as $key => $days) {
            $ranges[$key] = $this->rangePayload($world, $days ? now()->subDays($days) : null);
        }

        return [
            'default_range' => '30d',
            'range_options' => [
                ['value' => '7d', 'label' => 'Last 7 days'],
                ['value' => '30d', 'label' => 'Last 30 days'],
                ['value' => 'all', 'label' => 'Lifetime'],
            ],
            'ranges' => $ranges,
            'edition_comparison' => $this->editionComparisonPayload($world),
        ];
    }

    public function portfolioReport(): array
    {
        $ranges = [];

        foreach (self::PORTFOLIO_RANGE_WINDOWS as $key => $days) {
            $ranges[$key] = $this->portfolioRangePayload($days ? now()->subDays($days) : null);
        }

        return [
            'default_range' => '30d',
            'range_options' => [
                ['value' => '30d', 'label' => 'Last 30 days'],
                ['value' => 'all', 'label' => 'Lifetime'],
            ],
            'ranges' => $ranges,
        ];
    }

    public function sourceSurfaceLabel(?string $surface): string
    {
        return match ((string) $surface) {
            self::SOURCE_HOMEPAGE_SPOTLIGHT => 'Homepage spotlight',
            self::SOURCE_HOMEPAGE_WORLDS_RAIL => 'Homepage worlds rail',
            self::SOURCE_WORLDS_INDEX => 'Worlds index',
            self::SOURCE_NAVIGATION => 'Navigation',
            self::SOURCE_UPLOAD_FLOW => 'Upload flow',
            self::SOURCE_CHALLENGE_PAGE => 'Challenge page',
            self::SOURCE_NEWS_ARTICLE => 'News article',
            self::SOURCE_PROFILE => 'Profile',
            self::SOURCE_DIRECT => 'Direct',
            default => 'Unknown',
        };
    }

    private function rangePayload(World $world, ?Carbon $start): array
    {
        $eventCounts = $this->eventCounts($world, $start);
        $currentSubmissionCounts = $this->currentSubmissionCounts($world);
        $submissionActivity = $this->submissionActivityCounts($world, $start, $eventCounts);
        $rewardCounts = $this->rewardCounts($world, $start);
        $sources = $this->sourceBreakdown($world, $start);
        $sectionPerformance = $this->sectionPerformance($world, $start);
        $entityPerformance = $this->entityPerformance($world, $start);
        $ctaPerformance = $this->ctaPerformance($world, $start);
        $challengeMetrics = $this->challengeMetrics($world, $start, $eventCounts);
        $promotionImpressions = (int) ($eventCounts[self::EVENT_SOURCE_IMPRESSION] ?? 0);
        $sourceClicks = (int) ($eventCounts[self::EVENT_SOURCE_CLICKED] ?? 0);
        $views = (int) ($eventCounts[self::EVENT_VIEWED] ?? 0);
        $uniqueVisitors = $this->uniqueViewers($world, $start);
        $ctaClicks = (int) ($eventCounts[self::EVENT_CTA_CLICKED] ?? 0);
        $topSource = collect($sources)->sortByDesc('views')->first();
        $topSection = collect($sectionPerformance)->sortByDesc('clicks')->first();
        $topEntity = collect($entityPerformance)->sortByDesc('clicks')->first();

        return [
            'summary' => [
                'views' => $views,
                'unique_visitors' => $uniqueVisitors,
                'promotion_impressions' => $promotionImpressions,
                'cta_clicks' => $ctaClicks,
                'submissions' => $submissionActivity['submitted'],
                'approved_live_participations' => $currentSubmissionCounts['live'],
                'featured_participations' => $currentSubmissionCounts['featured'],
                'reward_grants' => $rewardCounts['total'],
                'challenge_clicks' => $challengeMetrics['total_clicks'],
                'approval_rate' => $submissionActivity['approval_rate'],
                'promotion_clickthrough_rate' => $promotionImpressions > 0 ? round($sourceClicks / $promotionImpressions, 4) : 0.0,
                'view_to_submission_conversion' => $submissionActivity['view_to_submission_conversion'],
                'top_source_surface' => $topSource ? [
                    'key' => $topSource['source_surface'],
                    'label' => $this->sourceSurfaceLabel($topSource['source_surface']),
                    'views' => $topSource['views'],
                    'impressions' => $topSource['impressions'],
                    'clickthrough_rate' => $topSource['clickthrough_rate'],
                ] : null,
                'top_clicked_section' => $topSection,
                'top_clicked_entity' => $topEntity,
            ],
            'traffic' => [
                'views' => $views,
                'unique_visitors' => $uniqueVisitors,
                'trend' => $this->trafficTrend($world, $start),
            ],
            'sources' => $sources,
            'cta_performance' => $ctaPerformance,
            'section_performance' => $sectionPerformance,
            'entity_performance' => $entityPerformance,
            'participation' => [
                ...$currentSubmissionCounts,
                ...$submissionActivity,
            ],
            'challenge' => $challengeMetrics,
            'rewards' => $rewardCounts,
        ];
    }

    private function eventCounts(World $world, ?Carbon $start): array
    {
        return $this->baseEventQuery($world, $start)
            ->select('event_type', DB::raw('COUNT(*) as total'))
            ->groupBy('event_type')
            ->pluck('total', 'event_type')
            ->map(fn ($count): int => (int) $count)
            ->all();
    }

    private function uniqueViewers(World $world, ?Carbon $start): int
    {
        return (int) $this->baseEventQuery($world, $start)
            ->where('event_type', self::EVENT_VIEWED)
            ->distinct('visitor_key')
            ->count('visitor_key');
    }

    private function sourceBreakdown(World $world, ?Carbon $start): array
    {
        $impressions = $this->baseEventQuery($world, $start)
            ->where('event_type', self::EVENT_SOURCE_IMPRESSION)
            ->select('source_surface', DB::raw('COUNT(*) as impressions'))
            ->groupBy('source_surface')
            ->get()
            ->keyBy(fn (WorldAnalyticsEvent $event): string => (string) ($event->source_surface ?: self::SOURCE_UNKNOWN));

        $views = $this->baseEventQuery($world, $start)
            ->where('event_type', self::EVENT_VIEWED)
            ->select('source_surface', DB::raw('COUNT(*) as views'), DB::raw('COUNT(DISTINCT visitor_key) as unique_visitors'))
            ->groupBy('source_surface')
            ->get()
            ->keyBy(fn (WorldAnalyticsEvent $event): string => (string) ($event->source_surface ?: self::SOURCE_UNKNOWN));

        $clicks = $this->baseEventQuery($world, $start)
            ->where('event_type', self::EVENT_SOURCE_CLICKED)
            ->select('source_surface', DB::raw('COUNT(*) as clicks'))
            ->groupBy('source_surface')
            ->get()
            ->keyBy(fn (WorldAnalyticsEvent $event): string => (string) ($event->source_surface ?: self::SOURCE_UNKNOWN));

        return collect($this->allowedSourceSurfaces())
            ->map(function (string $surface) use ($impressions, $views, $clicks): array {
                $impressionRow = $impressions->get($surface);
                $viewRow = $views->get($surface);
                $clickRow = $clicks->get($surface);
                $impressionCount = (int) ($impressionRow?->impressions ?? 0);
                $clickCount = (int) ($clickRow?->clicks ?? 0);
                $viewCount = (int) ($viewRow?->views ?? 0);

                return [
                    'source_surface' => $surface,
                    'label' => $this->sourceSurfaceLabel($surface),
                    'impressions' => $impressionCount,
                    'views' => $viewCount,
                    'unique_visitors' => (int) ($viewRow?->unique_visitors ?? 0),
                    'clicks' => $clickCount,
                    'clickthrough_rate' => $impressionCount > 0 ? round($clickCount / $impressionCount, 4) : 0.0,
                    'visit_rate' => $impressionCount > 0 ? round($viewCount / $impressionCount, 4) : 0.0,
                ];
            })
            ->filter(fn (array $row): bool => $row['impressions'] > 0 || $row['views'] > 0 || $row['clicks'] > 0)
            ->sortByDesc('views')
            ->values()
            ->all();
    }

    private function sectionPerformance(World $world, ?Carbon $start): array
    {
        return $this->baseEventQuery($world, $start)
            ->whereNotNull('section_key')
            ->whereIn('event_type', [
                self::EVENT_SECTION_CLICKED,
                self::EVENT_CTA_CLICKED,
                self::EVENT_ENTITY_CLICKED,
                self::EVENT_CHALLENGE_CTA_CLICKED,
            ])
            ->select('section_key', DB::raw('COUNT(*) as clicks'))
            ->groupBy('section_key')
            ->orderByDesc('clicks')
            ->get()
            ->map(fn (WorldAnalyticsEvent $event): array => [
                'section_key' => (string) $event->section_key,
                'label' => Str::headline((string) $event->section_key),
                'clicks' => (int) $event->clicks,
            ])
            ->all();
    }

    private function entityPerformance(World $world, ?Carbon $start): array
    {
        return $this->baseEventQuery($world, $start)
            ->where('event_type', self::EVENT_ENTITY_CLICKED)
            ->whereNotNull('entity_type')
            ->whereNotNull('entity_id')
            ->select('section_key', 'entity_type', 'entity_id', 'entity_title', DB::raw('COUNT(*) as clicks'))
            ->groupBy('section_key', 'entity_type', 'entity_id', 'entity_title')
            ->orderByDesc('clicks')
            ->limit(8)
            ->get()
            ->map(fn (WorldAnalyticsEvent $event): array => [
                'section_key' => (string) ($event->section_key ?? ''),
                'entity_type' => (string) ($event->entity_type ?? ''),
                'entity_id' => (int) ($event->entity_id ?? 0),
                'entity_title' => (string) ($event->entity_title ?: Str::headline((string) $event->entity_type)),
                'clicks' => (int) $event->clicks,
            ])
            ->all();
    }

    private function ctaPerformance(World $world, ?Carbon $start): array
    {
        return $this->baseEventQuery($world, $start)
            ->whereIn('event_type', [self::EVENT_CTA_CLICKED, self::EVENT_CHALLENGE_CTA_CLICKED])
            ->select('event_type', 'section_key', 'cta_key', DB::raw('COUNT(*) as clicks'))
            ->groupBy('event_type', 'section_key', 'cta_key')
            ->orderByDesc('clicks')
            ->get()
            ->map(fn (WorldAnalyticsEvent $event): array => [
                'event_type' => (string) $event->event_type,
                'section_key' => (string) ($event->section_key ?? ''),
                'cta_key' => (string) ($event->cta_key ?? ''),
                'label' => $this->ctaLabel((string) ($event->cta_key ?? '')),
                'clicks' => (int) $event->clicks,
            ])
            ->all();
    }

    private function currentSubmissionCounts(World $world): array
    {
        $counts = WorldSubmission::query()
            ->where('world_id', (int) $world->id)
            ->select('status', DB::raw('COUNT(*) as total'))
            ->groupBy('status')
            ->pluck('total', 'status');

        $featured = (int) WorldSubmission::query()
            ->where('world_id', (int) $world->id)
            ->where('status', WorldSubmission::STATUS_LIVE)
            ->where('is_featured', true)
            ->count();

        return [
            'pending' => (int) ($counts[WorldSubmission::STATUS_PENDING] ?? 0),
            'live' => (int) ($counts[WorldSubmission::STATUS_LIVE] ?? 0),
            'removed' => (int) ($counts[WorldSubmission::STATUS_REMOVED] ?? 0),
            'blocked' => (int) ($counts[WorldSubmission::STATUS_BLOCKED] ?? 0),
            'featured' => $featured,
        ];
    }

    private function submissionActivityCounts(World $world, ?Carbon $start, array $eventCounts): array
    {
        $createdQuery = WorldSubmission::query()->where('world_id', (int) $world->id);
        if ($start) {
            $createdQuery->where('created_at', '>=', $start);
        }

        $submitted = (int) $createdQuery->count();
        $approved = (int) ($eventCounts[self::EVENT_SUBMISSION_APPROVED] ?? 0);
        $removed = (int) ($eventCounts[self::EVENT_SUBMISSION_REMOVED] ?? 0);
        $blocked = (int) ($eventCounts[self::EVENT_SUBMISSION_BLOCKED] ?? 0);
        $featured = (int) ($eventCounts[self::EVENT_SUBMISSION_FEATURED] ?? 0);
        $views = max(1, (int) ($eventCounts[self::EVENT_VIEWED] ?? 0));

        return [
            'submitted' => $submitted,
            'approved' => $approved,
            'removed_actions' => $removed,
            'blocked_actions' => $blocked,
            'featured_actions' => $featured,
            'approval_rate' => $submitted > 0 ? round($approved / $submitted, 4) : 0.0,
            'removal_rate' => $submitted > 0 ? round($removed / $submitted, 4) : 0.0,
            'block_rate' => $submitted > 0 ? round($blocked / $submitted, 4) : 0.0,
            'view_to_submission_conversion' => round($submitted / $views, 4),
        ];
    }

    private function rewardCounts(World $world, ?Carbon $start): array
    {
        $query = WorldRewardGrant::query()->where('world_id', (int) $world->id);
        if ($start) {
            $query->where('granted_at', '>=', $start);
        }

        $counts = $query
            ->select('reward_type', DB::raw('COUNT(*) as total'))
            ->groupBy('reward_type')
            ->pluck('total', 'reward_type');

        return [
            'total' => (int) collect($counts)->sum(),
            'participant' => (int) ($counts[WorldRewardType::Participant->value] ?? 0),
            'featured' => (int) ($counts[WorldRewardType::Featured->value] ?? 0),
            'finalist' => (int) ($counts[WorldRewardType::Finalist->value] ?? 0),
            'winner' => (int) ($counts[WorldRewardType::Winner->value] ?? 0),
            'spotlight' => (int) ($counts[WorldRewardType::Spotlight->value] ?? 0),
        ];
    }

    private function challengeMetrics(World $world, ?Carbon $start, array $eventCounts): array
    {
        $query = $this->baseEventQuery($world, $start)->whereNotNull('challenge_id');
        $recapClicks = (clone $query)
            ->where('event_type', self::EVENT_CHALLENGE_CTA_CLICKED)
            ->whereIn('cta_key', ['challenge_story', 'challenge_recap'])
            ->count();
        $entryClicks = (clone $query)
            ->where('event_type', self::EVENT_ENTITY_CLICKED)
            ->where('section_key', 'challenge_entries')
            ->count();
        $winnerClicks = (clone $query)
            ->where('event_type', self::EVENT_ENTITY_CLICKED)
            ->where('section_key', 'challenge_winners')
            ->count();
        $finalistClicks = (clone $query)
            ->where('event_type', self::EVENT_ENTITY_CLICKED)
            ->where('section_key', 'challenge_finalists')
            ->count();
        $challengeCtaClicks = (int) ($eventCounts[self::EVENT_CHALLENGE_CTA_CLICKED] ?? 0);
        $submissionStarts = (int) ($eventCounts[self::EVENT_SUBMISSION_STARTED] ?? 0);
        $submissionsCreated = (int) ($eventCounts[self::EVENT_SUBMISSION_CREATED] ?? 0);
        $totalClicks = $challengeCtaClicks + (int) $recapClicks + (int) $entryClicks + (int) $winnerClicks + (int) $finalistClicks;

        return [
            'linked_challenge_id' => $world->linked_challenge_id ? (int) $world->linked_challenge_id : null,
            'challenge_cta_clicks' => $challengeCtaClicks,
            'recap_clicks' => (int) $recapClicks,
            'entry_clicks' => (int) $entryClicks,
            'winner_clicks' => (int) $winnerClicks,
            'finalist_clicks' => (int) $finalistClicks,
            'submission_starts' => $submissionStarts,
            'submissions_created' => $submissionsCreated,
            'click_to_submission_start_conversion' => $totalClicks > 0 ? round($submissionStarts / $totalClicks, 4) : 0.0,
            'click_to_submission_conversion' => $totalClicks > 0 ? round($submissionsCreated / $totalClicks, 4) : 0.0,
            'total_clicks' => $totalClicks,
        ];
    }

    private function portfolioRangePayload(?Carbon $start): array
    {
        $worlds = World::query()
            ->select(['id', 'title', 'slug', 'recurrence_key', 'edition_year'])
            ->get()
            ->keyBy('id');

        $viewRows = WorldAnalyticsEvent::query()
            ->when($start, fn (Builder $builder): Builder => $builder->where('occurred_at', '>=', $start))
            ->where('event_type', self::EVENT_VIEWED)
            ->select('world_id', DB::raw('COUNT(*) as views'), DB::raw('COUNT(DISTINCT visitor_key) as unique_visitors'))
            ->groupBy('world_id')
            ->get()
            ->keyBy('world_id');

        $impressionRows = WorldAnalyticsEvent::query()
            ->when($start, fn (Builder $builder): Builder => $builder->where('occurred_at', '>=', $start))
            ->where('event_type', self::EVENT_SOURCE_IMPRESSION)
            ->select('world_id', DB::raw('COUNT(*) as impressions'))
            ->groupBy('world_id')
            ->get()
            ->keyBy('world_id');

        $sourceClickRows = WorldAnalyticsEvent::query()
            ->when($start, fn (Builder $builder): Builder => $builder->where('occurred_at', '>=', $start))
            ->where('event_type', self::EVENT_SOURCE_CLICKED)
            ->select('world_id', DB::raw('COUNT(*) as source_clicks'))
            ->groupBy('world_id')
            ->get()
            ->keyBy('world_id');

        $submissionRows = WorldSubmission::query()
            ->when($start, fn (Builder $builder): Builder => $builder->where('created_at', '>=', $start))
            ->select('world_id', DB::raw('COUNT(*) as submissions'))
            ->groupBy('world_id')
            ->get()
            ->keyBy('world_id');

        $rewardRows = WorldRewardGrant::query()
            ->when($start, fn (Builder $builder): Builder => $builder->where('granted_at', '>=', $start))
            ->select('world_id', DB::raw('COUNT(*) as reward_grants'))
            ->groupBy('world_id')
            ->get()
            ->keyBy('world_id');

        $trackedWorldIds = collect()
            ->merge($viewRows->keys())
            ->merge($impressionRows->keys())
            ->merge($sourceClickRows->keys())
            ->merge($submissionRows->keys())
            ->merge($rewardRows->keys())
            ->map(fn ($id): int => (int) $id)
            ->unique()
            ->values();

        $rows = $trackedWorldIds
            ->map(function (int $worldId) use ($worlds, $viewRows, $impressionRows, $sourceClickRows, $submissionRows, $rewardRows): ?array {
                /** @var World|null $world */
                $world = $worlds->get($worldId);

                if (! $world) {
                    return null;
                }

                $views = (int) ($viewRows->get($worldId)?->views ?? 0);
                $uniqueVisitors = (int) ($viewRows->get($worldId)?->unique_visitors ?? 0);
                $impressions = (int) ($impressionRows->get($worldId)?->impressions ?? 0);
                $sourceClicks = (int) ($sourceClickRows->get($worldId)?->source_clicks ?? 0);
                $submissions = (int) ($submissionRows->get($worldId)?->submissions ?? 0);
                $rewardGrants = (int) ($rewardRows->get($worldId)?->reward_grants ?? 0);

                return [
                    'world_id' => (int) $world->id,
                    'title' => (string) $world->title,
                    'slug' => (string) $world->slug,
                    'edition_year' => $world->edition_year ? (int) $world->edition_year : null,
                    'recurrence_key' => $this->nullableString($world->recurrence_key),
                    'edit_url' => route('studio.worlds.edit', ['world' => $world->id]),
                    'public_url' => $world->publicUrl(),
                    'views' => $views,
                    'unique_visitors' => $uniqueVisitors,
                    'impressions' => $impressions,
                    'source_clicks' => $sourceClicks,
                    'submissions' => $submissions,
                    'reward_grants' => $rewardGrants,
                    'view_to_submission_conversion' => $views > 0 ? round($submissions / $views, 4) : 0.0,
                    'promotion_clickthrough_rate' => $impressions > 0 ? round($sourceClicks / $impressions, 4) : 0.0,
                ];
            })
            ->filter()
            ->values();

        return [
            'summary' => [
                'tracked_worlds' => $rows->count(),
                'views' => (int) $rows->sum('views'),
                'unique_visitors' => (int) $rows->sum('unique_visitors'),
                'promotion_impressions' => (int) $rows->sum('impressions'),
                'submissions' => (int) $rows->sum('submissions'),
                'reward_grants' => (int) $rows->sum('reward_grants'),
            ],
            'leaderboards' => [
                'views' => $rows->sortByDesc('views')->take(5)->values()->all(),
                'unique_visitors' => $rows->sortByDesc('unique_visitors')->take(5)->values()->all(),
                'submissions' => $rows->sortByDesc('submissions')->take(5)->values()->all(),
                'reward_grants' => $rows->sortByDesc('reward_grants')->take(5)->values()->all(),
                'conversion' => $rows
                    ->filter(fn (array $row): bool => $row['views'] > 0 || $row['submissions'] > 0)
                    ->sortByDesc('view_to_submission_conversion')
                    ->take(5)
                    ->values()
                    ->all(),
            ],
        ];
    }

    private function trafficTrend(World $world, ?Carbon $start): array
    {
        $events = $this->baseEventQuery($world, $start)
            ->where('event_type', self::EVENT_VIEWED)
            ->orderBy('occurred_at')
            ->pluck('occurred_at')
            ->map(fn ($timestamp): Carbon => Carbon::parse($timestamp));

        if ($events->isEmpty()) {
            return [];
        }

        $bucketByMonth = $start === null && $events->first()?->diffInDays($events->last()) > 90;

        return $events
            ->groupBy(fn (Carbon $date): string => $bucketByMonth ? $date->format('Y-m') : $date->toDateString())
            ->map(fn (Collection $items, string $label): array => [
                'label' => $label,
                'views' => $items->count(),
            ])
            ->values()
            ->all();
    }

    private function editionComparisonPayload(World $world): ?array
    {
        $recurrenceKey = trim((string) ($world->recurrence_key ?? ''));
        if ($recurrenceKey === '') {
            return null;
        }

        $editions = World::query()
            ->where('recurrence_key', $recurrenceKey)
            ->orderByDesc('edition_year')
            ->orderByDesc('starts_at')
            ->get();

        if ($editions->count() < 2) {
            return null;
        }

        $worldIds = $editions->pluck('id')->map(fn ($id): int => (int) $id)->all();

        $viewCounts = WorldAnalyticsEvent::query()
            ->whereIn('world_id', $worldIds)
            ->where('event_type', self::EVENT_VIEWED)
            ->select('world_id', DB::raw('COUNT(*) as total_views'), DB::raw('COUNT(DISTINCT visitor_key) as unique_visitors'))
            ->groupBy('world_id')
            ->get()
            ->keyBy('world_id');

        $submissionCounts = WorldSubmission::query()
            ->whereIn('world_id', $worldIds)
            ->select(
                'world_id',
                DB::raw('COUNT(*) as submitted_total'),
                DB::raw("SUM(CASE WHEN status = 'live' THEN 1 ELSE 0 END) as live_total"),
                DB::raw("SUM(CASE WHEN is_featured = 1 AND status = 'live' THEN 1 ELSE 0 END) as featured_total")
            )
            ->groupBy('world_id')
            ->get()
            ->keyBy('world_id');

        $challengeClicks = WorldAnalyticsEvent::query()
            ->whereIn('world_id', $worldIds)
            ->whereIn('event_type', [self::EVENT_CHALLENGE_CTA_CLICKED, self::EVENT_ENTITY_CLICKED])
            ->where(function (Builder $builder): void {
                $builder->whereNotNull('challenge_id')
                    ->orWhereIn('section_key', ['challenge_entries', 'challenge_winners', 'challenge_finalists', 'challenge']);
            })
            ->select('world_id', DB::raw('COUNT(*) as total_challenge_clicks'))
            ->groupBy('world_id')
            ->get()
            ->keyBy('world_id');

        $rewardCounts = WorldRewardGrant::query()
            ->whereIn('world_id', $worldIds)
            ->select('world_id', DB::raw('COUNT(*) as total_rewards'))
            ->groupBy('world_id')
            ->get()
            ->keyBy('world_id');

        return [
            'recurrence_key' => $recurrenceKey,
            'label' => trim((string) ($world->title ?: Str::headline($recurrenceKey))),
            'editions' => $editions->map(function (World $edition) use ($world, $viewCounts, $submissionCounts, $challengeClicks, $rewardCounts): array {
                $views = $viewCounts->get((int) $edition->id);
                $submissions = $submissionCounts->get((int) $edition->id);
                $challenge = $challengeClicks->get((int) $edition->id);
                $rewards = $rewardCounts->get((int) $edition->id);

                return [
                    'world_id' => (int) $edition->id,
                    'title' => (string) $edition->title,
                    'edition_year' => $edition->edition_year ? (int) $edition->edition_year : null,
                    'public_url' => $edition->publicUrl(),
                    'is_current_world' => (int) $edition->id === (int) $world->id,
                    'metrics' => [
                        'views' => (int) ($views?->total_views ?? 0),
                        'unique_visitors' => (int) ($views?->unique_visitors ?? 0),
                        'submissions' => (int) ($submissions?->submitted_total ?? 0),
                        'live_participations' => (int) ($submissions?->live_total ?? 0),
                        'featured_participations' => (int) ($submissions?->featured_total ?? 0),
                        'challenge_clicks' => (int) ($challenge?->total_challenge_clicks ?? 0),
                        'reward_grants' => (int) ($rewards?->total_rewards ?? 0),
                    ],
                ];
            })->all(),
        ];
    }

    private function baseEventQuery(World $world, ?Carbon $start): Builder
    {
        return WorldAnalyticsEvent::query()
            ->where('world_id', (int) $world->id)
            ->when($start, fn (Builder $builder): Builder => $builder->where('occurred_at', '>=', $start));
    }

    private function resolveVisitorKey(Request $request, ?User $user, string $visitorToken): string
    {
        if ($user) {
            return hash('sha256', 'user:' . $user->id);
        }

        $token = trim($visitorToken);
        if ($token !== '') {
            return hash('sha256', 'visitor:' . $token);
        }

        $sessionId = $request->session()->getId();
        if ($sessionId !== '') {
            return hash('sha256', 'session:' . $sessionId);
        }

        return hash('sha256', 'fallback:' . ($request->ip() ?? 'unknown') . '|' . Str::limit((string) $request->userAgent(), 120, ''));
    }

    private function sanitizeMeta(array $meta): ?array
    {
        $sanitized = collect($meta)
            ->map(function ($value) {
                if (is_scalar($value) || $value === null) {
                    return $value;
                }

                if (is_array($value)) {
                    return collect($value)
                        ->filter(fn ($entry) => is_scalar($entry) || $entry === null)
                        ->map(fn ($entry) => is_string($entry) ? Str::limit($entry, 200, '') : $entry)
                        ->values()
                        ->all();
                }

                return null;
            })
            ->filter(fn ($value) => $value !== null && $value !== '')
            ->all();

        return $sanitized === [] ? null : $sanitized;
    }

    private function ctaLabel(string $ctaKey): string
    {
        return match ($ctaKey) {
            'main_world_cta' => 'Main world CTA',
            'badge_cta' => 'Badge CTA',
            'challenge_primary' => 'Challenge primary CTA',
            'challenge_story' => 'Challenge story CTA',
            'challenge_recap' => 'Challenge recap CTA',
            'challenge_direct' => 'Direct challenge CTA',
            'linked_group' => 'Linked group CTA',
            'family_route' => 'Family route CTA',
            'edition_archive' => 'Edition archive CTA',
            'supporting_item' => 'Supporting item CTA',
            default => Str::headline(str_replace('_', ' ', $ctaKey ?: 'cta')),
        };
    }

    private function nullableString(mixed $value): ?string
    {
        $text = trim((string) ($value ?? ''));

        return $text === '' ? null : Str::limit($text, 180, '');
    }
}