<?php

declare(strict_types=1);

namespace App\Http\Controllers\Settings;

use App\Http\Controllers\Controller;
use App\Models\AcademyContentMetricDaily;
use App\Models\AcademyEvent;
use App\Models\AcademySearchLog;
use App\Services\Academy\AcademyAnalyticsContentResolver;
use App\Services\Academy\AcademyContentIntelligenceService;
use App\Services\Academy\AcademyPopularityService;
use App\Support\AcademyAnalytics\AcademyAnalyticsContentType;
use App\Support\AcademyAnalytics\AcademyAnalyticsEventType;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Inertia\Inertia;
use Inertia\Response;

final class AcademyAdminAnalyticsController extends Controller
{
    public function __construct(
        private readonly AcademyPopularityService $popularity,
        private readonly AcademyAnalyticsContentResolver $resolver,
        private readonly AcademyContentIntelligenceService $intelligence,
    ) {}

    public function overview(Request $request): Response
    {
        [$from, $to, $range] = $this->resolveDateRange($request);
        $promptLibraryCurrent = $this->contentSummary(AcademyAnalyticsContentType::PROMPT_LIBRARY, $from, $to);
        [$previousFrom, $previousTo] = $this->previousRange($from, $to);
        $promptLibraryPrevious = $this->contentSummary(AcademyAnalyticsContentType::PROMPT_LIBRARY, $previousFrom, $previousTo);

        $summary = $this->metricsQuery($from, $to)
            ->selectRaw('sum(views) as views, sum(unique_visitors) as unique_visitors, sum(user_views) as user_views, sum(guest_views) as guest_views, sum(subscriber_views) as subscriber_views, sum(prompt_copies) as prompt_copies, sum(likes) as likes, sum(saves) as saves, sum(completions) as completions, sum(starts) as starts, sum(upgrade_clicks) as upgrade_clicks')
            ->first();

        return Inertia::render('Admin/Academy/AnalyticsOverview', [
            'nav' => $this->nav(),
            'range' => $this->rangePayload($range, $from, $to),
            'stats' => [
                'views' => (int) ($summary?->views ?? 0),
                'uniqueVisitors' => (int) ($summary?->unique_visitors ?? 0),
                'userViews' => (int) ($summary?->user_views ?? 0),
                'guestViews' => (int) ($summary?->guest_views ?? 0),
                'subscriberViews' => (int) ($summary?->subscriber_views ?? 0),
                'promptCopies' => (int) ($summary?->prompt_copies ?? 0),
                'likes' => (int) ($summary?->likes ?? 0),
                'saves' => (int) ($summary?->saves ?? 0),
                'lessonCompletions' => (int) ($summary?->completions ?? 0),
                'courseStarts' => (int) ($summary?->starts ?? 0),
                'upgradeClicks' => (int) ($summary?->upgrade_clicks ?? 0),
            ],
            'promptLibraryTrend' => [
                'current' => $promptLibraryCurrent,
                'previous' => $promptLibraryPrevious,
                'deltas' => [
                    'views' => $this->percentDelta((int) $promptLibraryCurrent['views'], (int) $promptLibraryPrevious['views']),
                    'uniqueVisitors' => $this->percentDelta((int) $promptLibraryCurrent['uniqueVisitors'], (int) $promptLibraryPrevious['uniqueVisitors']),
                    'engagedViews' => $this->percentDelta((int) $promptLibraryCurrent['engagedViews'], (int) $promptLibraryPrevious['engagedViews']),
                    'engagementRate' => $this->percentDelta((float) $promptLibraryCurrent['engagementRate'], (float) $promptLibraryPrevious['engagementRate']),
                ],
                'range' => [
                    'current' => ['from' => $from->toDateString(), 'to' => $to->toDateString()],
                    'previous' => ['from' => $previousFrom->toDateString(), 'to' => $previousTo->toDateString()],
                ],
            ],
            'popularPromptPeriodUsage' => $this->popularPromptPeriodUsage($from, $to),
            'topContent' => $this->serializeContentRows($this->popularity->topContent($from, $to, 8)),
            'topWeek' => $this->serializeContentRows($this->popularity->topContent(now()->subDays(6)->startOfDay(), now()->endOfDay(), 8)),
        ]);
    }

    public function content(Request $request): Response
    {
        return $this->renderContentPage($request, null, 'Content performance', 'Cross-module performance across prompts, lessons, courses, packs, and challenges.');
    }

    public function prompts(Request $request): Response
    {
        return $this->renderContentPage($request, AcademyAnalyticsContentType::PROMPT, 'Prompt analytics', 'Copy-heavy prompt performance, save rates, and upgrade interest.');
    }

    public function promptLibrary(Request $request): Response
    {
        return $this->renderContentPage($request, AcademyAnalyticsContentType::PROMPT_LIBRARY, 'Prompt library analytics', 'Discovery and engagement on the public /academy/prompts library page.');
    }

    public function lessons(Request $request): Response
    {
        return $this->renderContentPage($request, AcademyAnalyticsContentType::LESSON, 'Lesson analytics', 'Lesson engagement, starts, completions, and drop-off signals.');
    }

    public function courses(Request $request): Response
    {
        return $this->renderContentPage($request, AcademyAnalyticsContentType::COURSE, 'Course analytics', 'Course views, starts, completion progress, and upgrade intent.');
    }

    public function search(Request $request): Response
    {
        [$from, $to, $range] = $this->resolveDateRange($request);

        $searchQuery = AcademySearchLog::query()->whereBetween('created_at', [$from, $to]);
        $searchLogs = (clone $searchQuery)->latest('created_at')->limit(500)->get();

        return Inertia::render('Admin/Academy/AnalyticsSearch', [
            'nav' => $this->nav(),
            'range' => $this->rangePayload($range, $from, $to),
            'summary' => [
                'searches' => (int) (clone $searchQuery)->count(),
                'zeroResultSearches' => (int) (clone $searchQuery)->where('results_count', 0)->count(),
                'loggedInSearches' => (int) (clone $searchQuery)->where('is_logged_in', true)->count(),
                'subscriberSearches' => (int) (clone $searchQuery)->where('is_subscriber', true)->count(),
                'searchesWithClicks' => (int) (clone $searchQuery)->whereNotNull('clicked_content_id')->count(),
            ],
            'topSearches' => (clone $searchQuery)
                ->selectRaw('normalized_query, max(query) as query, count(*) as searches, sum(results_count = 0) as zero_result_hits, avg(results_count) as avg_results, sum(case when clicked_content_id is not null then 1 else 0 end) as clicks')
                ->groupBy('normalized_query')
                ->orderByDesc('searches')
                ->limit(20)
                ->get()
                ->map(fn ($row): array => [
                    'query' => (string) ($row->query ?: $row->normalized_query),
                    'normalized_query' => (string) $row->normalized_query,
                    'searches' => (int) $row->searches,
                    'zero_result_hits' => (int) $row->zero_result_hits,
                    'avg_results' => round((float) $row->avg_results, 1),
                    'clicks' => (int) ($row->clicks ?? 0),
                    'click_through_rate' => (int) $row->searches > 0 ? round((((int) ($row->clicks ?? 0)) / (int) $row->searches) * 100, 1) : 0,
                ])
                ->all(),
            'zeroResults' => (clone $searchQuery)
                ->selectRaw('normalized_query, max(query) as query, count(*) as searches')
                ->where('results_count', 0)
                ->groupBy('normalized_query')
                ->orderByDesc('searches')
                ->limit(20)
                ->get()
                ->map(fn ($row): array => [
                    'query' => (string) ($row->query ?: $row->normalized_query),
                    'searches' => (int) $row->searches,
                ])
                ->all(),
            'lowClickThroughSearches' => (clone $searchQuery)
                ->selectRaw('normalized_query, max(query) as query, count(*) as searches, sum(case when clicked_content_id is not null then 1 else 0 end) as clicks, avg(results_count) as avg_results')
                ->groupBy('normalized_query')
                ->havingRaw('count(*) >= 2')
                ->orderByRaw('case when count(*) = 0 then 1 else (sum(case when clicked_content_id is not null then 1 else 0 end) * 1.0 / count(*)) end asc')
                ->limit(20)
                ->get()
                ->map(fn ($row): array => [
                    'query' => (string) ($row->query ?: $row->normalized_query),
                    'searches' => (int) $row->searches,
                    'clicks' => (int) ($row->clicks ?? 0),
                    'avg_results' => round((float) $row->avg_results, 1),
                    'click_through_rate' => (int) $row->searches > 0 ? round((((int) ($row->clicks ?? 0)) / (int) $row->searches) * 100, 1) : 0,
                ])
                ->all(),
            'highestClickThroughSearches' => (clone $searchQuery)
                ->selectRaw('normalized_query, max(query) as query, count(*) as searches, sum(case when clicked_content_id is not null then 1 else 0 end) as clicks, avg(results_count) as avg_results')
                ->groupBy('normalized_query')
                ->havingRaw('count(*) >= 2')
                ->orderByRaw('(sum(case when clicked_content_id is not null then 1 else 0 end) * 1.0 / count(*)) desc')
                ->limit(20)
                ->get()
                ->map(fn ($row): array => [
                    'query' => (string) ($row->query ?: $row->normalized_query),
                    'searches' => (int) $row->searches,
                    'clicks' => (int) ($row->clicks ?? 0),
                    'avg_results' => round((float) $row->avg_results, 1),
                    'click_through_rate' => (int) $row->searches > 0 ? round((((int) ($row->clicks ?? 0)) / (int) $row->searches) * 100, 1) : 0,
                ])
                ->all(),
            'searchesWithResultsNoClicks' => (clone $searchQuery)
                ->selectRaw('normalized_query, max(query) as query, count(*) as searches, avg(results_count) as avg_results')
                ->where('results_count', '>', 0)
                ->whereNull('clicked_content_id')
                ->groupBy('normalized_query')
                ->orderByDesc('searches')
                ->limit(20)
                ->get()
                ->map(fn ($row): array => [
                    'query' => (string) ($row->query ?: $row->normalized_query),
                    'searches' => (int) $row->searches,
                    'avg_results' => round((float) $row->avg_results, 1),
                    'clicks' => 0,
                    'click_through_rate' => 0,
                ])
                ->all(),
            'topClickedResults' => (clone $searchQuery)
                ->selectRaw('clicked_content_type, clicked_content_id, count(*) as clicks')
                ->whereNotNull('clicked_content_type')
                ->whereNotNull('clicked_content_id')
                ->groupBy('clicked_content_type', 'clicked_content_id')
                ->orderByDesc('clicks')
                ->limit(20)
                ->get()
                ->map(fn ($row): array => [
                    'title' => $this->resolver->title((string) $row->clicked_content_type, (int) $row->clicked_content_id),
                    'content_type' => (string) $row->clicked_content_type,
                    'content_id' => (int) $row->clicked_content_id,
                    'clicks' => (int) $row->clicks,
                ])
                ->all(),
            'filterUsage' => $this->summarizeSearchFilters($searchLogs),
            'recentSearches' => (clone $searchQuery)
                ->latest('created_at')
                ->limit(25)
                ->get()
                ->map(fn (AcademySearchLog $log): array => [
                    'query' => (string) $log->query,
                    'results_count' => (int) $log->results_count,
                    'logged_in' => (bool) $log->is_logged_in,
                    'subscriber' => (bool) $log->is_subscriber,
                    'clicked_content_type' => $log->clicked_content_type,
                    'has_click' => $log->clicked_content_id !== null,
                    'created_at' => $log->created_at?->toISOString(),
                ])
                ->all(),
        ]);
    }

    public function intelligence(Request $request): Response
    {
        [$from, $to, $range] = $this->resolveDateRange($request, '30d');
        $filters = [
            'from' => $from,
            'to' => $to,
            'limit' => 25,
        ];

        return Inertia::render('Admin/Academy/AnalyticsIntelligence', [
            'nav' => $this->nav(),
            'range' => $this->rangePayload($range, $from, $to),
            'contentOpportunities' => $this->intelligence->getContentOpportunities($filters),
            'searchGaps' => $this->intelligence->getSearchGaps($filters),
            'promptInsights' => $this->intelligence->getPromptInsights($filters),
            'lessonDropoffs' => $this->intelligence->getLessonDropoffs($filters),
            'courseHealth' => $this->intelligence->getCourseHealth($filters),
            'premiumInterest' => $this->intelligence->getPremiumInterest($filters),
            'editorialRecommendations' => $this->intelligence->getEditorialRecommendations($filters),
        ]);
    }

    /**
     * @param  Collection<int, AcademySearchLog>  $logs
     * @return list<array<string, int|string>>
     */
    private function summarizeSearchFilters(Collection $logs): array
    {
        $counts = [];

        foreach ($logs as $log) {
            $filters = is_array($log->filters) ? $log->filters : [];

            foreach ($filters as $key => $value) {
                if ($value === null || $value === '' || $key === 'q') {
                    continue;
                }

                $values = is_array($value) ? $value : [$value];

                foreach ($values as $rawValue) {
                    $label = trim((string) $rawValue);
                    if ($label === '') {
                        continue;
                    }

                    $bucket = sprintf('%s:%s', $key, $label);
                    $counts[$bucket] = [
                        'filter' => (string) $key,
                        'value' => $label,
                        'uses' => (int) (($counts[$bucket]['uses'] ?? 0) + 1),
                    ];
                }
            }
        }

        usort($counts, static fn (array $left, array $right): int => $right['uses'] <=> $left['uses']);

        return array_slice(array_values($counts), 0, 20);
    }

    public function funnel(Request $request): Response
    {
        [$from, $to, $range] = $this->resolveDateRange($request);

        $summary = $this->metricsQuery($from, $to)
            ->selectRaw('sum(unique_visitors) as unique_visitors, sum(premium_preview_views) as premium_preview_views, sum(upgrade_clicks) as upgrade_clicks, sum(starts) as starts, sum(completions) as completions')
            ->first();

        $bestConverters = $this->metricsQuery($from, $to)
            ->selectRaw('content_type, content_id, sum(unique_visitors) as unique_visitors, sum(premium_preview_views) as premium_preview_views, sum(upgrade_clicks) as upgrade_clicks, sum(conversion_score) as conversion_score')
            ->groupBy('content_type', 'content_id')
            ->havingRaw('sum(upgrade_clicks) > 0')
            ->orderByDesc('conversion_score')
            ->limit(12)
            ->get();

        return Inertia::render('Admin/Academy/AnalyticsFunnel', [
            'nav' => $this->nav(),
            'range' => $this->rangePayload($range, $from, $to),
            'summary' => [
                'academyVisitors' => (int) ($summary?->unique_visitors ?? 0),
                'premiumPreviewViews' => (int) ($summary?->premium_preview_views ?? 0),
                'upgradeClicks' => (int) ($summary?->upgrade_clicks ?? 0),
                'starts' => (int) ($summary?->starts ?? 0),
                'completions' => (int) ($summary?->completions ?? 0),
                'checkoutStarts' => 0,
                'subscriptions' => 0,
            ],
            'bestConverters' => $this->serializeContentRows($bestConverters, includeConversion: true),
        ]);
    }

    private function renderContentPage(Request $request, ?string $forcedContentType, string $title, string $subtitle): Response
    {
        [$from, $to, $range] = $this->resolveDateRange($request);
        $sort = (string) $request->query('sort', 'popularity_score');
        $direction = strtolower((string) $request->query('direction', 'desc')) === 'asc' ? 'asc' : 'desc';
        $access = trim((string) $request->query('access', ''));
        $contentType = $forcedContentType ?: (trim((string) $request->query('content_type', '')) ?: null);

        $query = $this->metricsQuery($from, $to)
            ->selectRaw('content_type, content_id, sum(views) as views, sum(unique_visitors) as unique_visitors, sum(engaged_views) as engaged_views, sum(likes) as likes, sum(saves) as saves, sum(prompt_copies) as prompt_copies, sum(starts) as starts, sum(completions) as completions, sum(upgrade_clicks) as upgrade_clicks, sum(popularity_score) as popularity_score, sum(conversion_score) as conversion_score')
            ->groupBy('content_type', 'content_id');

        if ($contentType !== null) {
            $query->where('content_type', $contentType);
        }

        $rows = $query->get();

        $serializedRows = $this->serializeContentRows($rows, includeConversion: true)
            ->filter(function (array $row) use ($access): bool {
                if ($access === '') {
                    return true;
                }

                return strtolower((string) ($row['access_level'] ?? '')) === strtolower($access);
            })
            ->sortBy($sort, SORT_REGULAR, $direction === 'desc')
            ->values()
            ->all();

        return Inertia::render('Admin/Academy/AnalyticsContent', [
            'nav' => $this->nav(),
            'range' => $this->rangePayload($range, $from, $to),
            'title' => $title,
            'subtitle' => $subtitle,
            'filters' => [
                'sort' => $sort,
                'direction' => $direction,
                'access' => $access,
                'content_type' => $contentType,
            ],
            'summary' => $contentType === AcademyAnalyticsContentType::PROMPT_LIBRARY
                ? $this->contentSummary(AcademyAnalyticsContentType::PROMPT_LIBRARY, $from, $to)
                : null,
            'rows' => $serializedRows,
            'contentTypeOptions' => [
                ['value' => '', 'label' => 'All content'],
                ['value' => AcademyAnalyticsContentType::PROMPT_LIBRARY, 'label' => 'Prompt library'],
                ['value' => AcademyAnalyticsContentType::PROMPT_PACK_LIBRARY, 'label' => 'Prompt pack library'],
                ['value' => AcademyAnalyticsContentType::PROMPT, 'label' => 'Prompts'],
                ['value' => AcademyAnalyticsContentType::LESSON, 'label' => 'Lessons'],
                ['value' => AcademyAnalyticsContentType::COURSE, 'label' => 'Courses'],
                ['value' => AcademyAnalyticsContentType::PROMPT_PACK, 'label' => 'Prompt packs'],
                ['value' => AcademyAnalyticsContentType::CHALLENGE, 'label' => 'Challenges'],
            ],
            'sortOptions' => [
                ['value' => 'views', 'label' => 'Views'],
                ['value' => 'unique_visitors', 'label' => 'Unique visitors'],
                ['value' => 'likes', 'label' => 'Likes'],
                ['value' => 'saves', 'label' => 'Saves'],
                ['value' => 'prompt_copies', 'label' => 'Copies'],
                ['value' => 'completions', 'label' => 'Completions'],
                ['value' => 'upgrade_clicks', 'label' => 'Upgrade clicks'],
                ['value' => 'popularity_score', 'label' => 'Popularity score'],
                ['value' => 'conversion_score', 'label' => 'Conversion score'],
            ],
        ]);
    }

    private function metricsQuery(Carbon $from, Carbon $to)
    {
        return AcademyContentMetricDaily::query()
            ->whereBetween('date', [$from->copy()->startOfDay(), $to->copy()->endOfDay()]);
    }

    /**
     * @return array<string, int|float>
     */
    private function contentSummary(string $contentType, Carbon $from, Carbon $to): array
    {
        $query = $this->metricsQuery($from, $to)
            ->where('content_type', $contentType);

        if (! AcademyAnalyticsContentType::requiresContentId($contentType)) {
            $query->whereNull('content_id');
        }

        $summary = $query
            ->selectRaw('sum(views) as views, sum(unique_visitors) as unique_visitors, sum(engaged_views) as engaged_views, sum(scroll_50) as scroll_50, sum(scroll_75) as scroll_75, sum(scroll_100) as scroll_100, avg(avg_engaged_seconds) as avg_engaged_seconds, sum(popularity_score) as popularity_score')
            ->first();

        $uniqueVisitors = max(0, (int) ($summary?->unique_visitors ?? 0));
        $engagedViews = max(0, (int) ($summary?->engaged_views ?? 0));
        $scroll100 = max(0, (int) ($summary?->scroll_100 ?? 0));

        return [
            'views' => max(0, (int) ($summary?->views ?? 0)),
            'uniqueVisitors' => $uniqueVisitors,
            'engagedViews' => $engagedViews,
            'scroll50' => max(0, (int) ($summary?->scroll_50 ?? 0)),
            'scroll75' => max(0, (int) ($summary?->scroll_75 ?? 0)),
            'scroll100' => $scroll100,
            'avgEngagedSeconds' => round((float) ($summary?->avg_engaged_seconds ?? 0), 1),
            'popularityScore' => round((float) ($summary?->popularity_score ?? 0), 2),
            'engagementRate' => $uniqueVisitors > 0 ? round(($engagedViews / $uniqueVisitors) * 100, 1) : 0.0,
            'deepScrollRate' => $uniqueVisitors > 0 ? round(($scroll100 / $uniqueVisitors) * 100, 1) : 0.0,
        ];
    }

    /**
     * @return array{0: Carbon, 1: Carbon}
     */
    private function previousRange(Carbon $from, Carbon $to): array
    {
        $days = $from->copy()->startOfDay()->diffInDays($to->copy()->startOfDay()) + 1;

        return [
            $from->copy()->subDays($days)->startOfDay(),
            $from->copy()->subDay()->endOfDay(),
        ];
    }

    private function percentDelta(int|float $current, int|float $previous): ?float
    {
        if ((float) $previous === 0.0) {
            return (float) $current === 0.0 ? 0.0 : null;
        }

        return round((((float) $current - (float) $previous) / (float) $previous) * 100, 1);
    }

    /**
     * @return array{totalViews:int,totalVisitors:int,periods:list<array<string,int|float|string>>}
     */
    private function popularPromptPeriodUsage(Carbon $from, Carbon $to): array
    {
        $events = AcademyEvent::query()
            ->whereBetween('occurred_at', [$from, $to])
            ->where('event_type', AcademyAnalyticsEventType::PAGE_VIEW)
            ->where('content_type', AcademyAnalyticsContentType::PROMPT_POPULAR)
            ->get(['visitor_id', 'metadata']);

        $summary = [];
        $totalViews = 0;
        $visitorBuckets = [];

        foreach ($events as $event) {
            $metadata = is_array($event->metadata) ? $event->metadata : [];
            $period = trim((string) ($metadata['period'] ?? ''));

            if ($period === '') {
                continue;
            }

            $days = max(0, (int) ($metadata['period_days'] ?? 0));

            if (! isset($summary[$period])) {
                $summary[$period] = [
                    'period' => $period,
                    'label' => sprintf('%s days', $days > 0 ? $days : (int) preg_replace('/\D+/', '', $period)),
                    'views' => 0,
                    'uniqueVisitors' => 0,
                    'share' => 0.0,
                    'days' => $days,
                ];
                $visitorBuckets[$period] = [];
            }

            $summary[$period]['views']++;
            $totalViews++;

            $visitorId = trim((string) ($event->visitor_id ?? ''));
            if ($visitorId !== '') {
                $visitorBuckets[$period][$visitorId] = true;
            }
        }

        $totalVisitors = 0;

        foreach ($summary as $period => &$row) {
            $uniqueVisitors = count($visitorBuckets[$period] ?? []);
            $row['uniqueVisitors'] = $uniqueVisitors;
            $row['share'] = $totalViews > 0 ? round((((int) $row['views']) / $totalViews) * 100, 1) : 0.0;
            $totalVisitors += $uniqueVisitors;
        }
        unset($row);

        usort($summary, static function (array $left, array $right): int {
            if ((int) $right['views'] === (int) $left['views']) {
                return ((int) $left['days']) <=> ((int) $right['days']);
            }

            return ((int) $right['views']) <=> ((int) $left['views']);
        });

        return [
            'totalViews' => $totalViews,
            'totalVisitors' => $totalVisitors,
            'periods' => array_values($summary),
        ];
    }

    /**
     * @param  Collection<int, mixed>  $rows
     * @return Collection<int, array<string, mixed>>
     */
    private function serializeContentRows(Collection $rows, bool $includeConversion = false): Collection
    {
        return $rows->map(function ($row) use ($includeConversion): array {
            $contentType = (string) $row->content_type;
            $contentId = $row->content_id ? (int) $row->content_id : null;
            $title = $this->resolver->title($contentType, $contentId);
            $accessLevel = $this->resolver->accessLevel($contentType, $contentId);
            $uniqueVisitors = max(0, (int) ($row->unique_visitors ?? 0));
            $promptCopies = max(0, (int) ($row->prompt_copies ?? 0));
            $likes = max(0, (int) ($row->likes ?? 0));
            $saves = max(0, (int) ($row->saves ?? 0));
            $starts = max(0, (int) ($row->starts ?? 0));
            $completions = max(0, (int) ($row->completions ?? 0));
            $premiumPreviewViews = max(0, (int) ($row->premium_preview_views ?? 0));
            $upgradeClicks = max(0, (int) ($row->upgrade_clicks ?? 0));

            return [
                'content_type' => $contentType,
                'content_type_label' => (string) Str::of(str_replace('academy_', '', $contentType))->replace('_', ' ')->headline(),
                'content_id' => $contentId,
                'title' => $title,
                'access_level' => $accessLevel,
                'views' => (int) ($row->views ?? 0),
                'unique_visitors' => $uniqueVisitors,
                'engaged_views' => (int) ($row->engaged_views ?? 0),
                'likes' => $likes,
                'saves' => $saves,
                'prompt_copies' => $promptCopies,
                'starts' => $starts,
                'completions' => $completions,
                'upgrade_clicks' => $upgradeClicks,
                'popularity_score' => round((float) ($row->popularity_score ?? 0), 2),
                'conversion_score' => round((float) ($row->conversion_score ?? 0), 2),
                'copy_rate' => $uniqueVisitors > 0 ? round(($promptCopies / $uniqueVisitors) * 100, 1) : 0,
                'save_rate' => $uniqueVisitors > 0 ? round(($saves / $uniqueVisitors) * 100, 1) : 0,
                'like_rate' => $uniqueVisitors > 0 ? round(($likes / $uniqueVisitors) * 100, 1) : 0,
                'completion_rate' => $starts > 0 ? round(($completions / $starts) * 100, 1) : 0,
                'upgrade_rate' => max(1, $premiumPreviewViews) > 0 ? round(($upgradeClicks / max(1, $premiumPreviewViews)) * 100, 1) : 0,
                'trend' => ((float) ($row->popularity_score ?? 0)) >= 100 ? 'High momentum' : (((float) ($row->popularity_score ?? 0)) >= 25 ? 'Building' : 'Early'),
                'include_conversion' => $includeConversion,
            ];
        });
    }

    /**
     * @return array{0: Carbon, 1: Carbon, 2: string}
     */
    private function resolveDateRange(Request $request, string $defaultRange = '7d'): array
    {
        $range = trim((string) $request->query('range', $defaultRange));

        return match ($range) {
            'today' => [now()->startOfDay(), now()->endOfDay(), 'today'],
            'yesterday' => [now()->subDay()->startOfDay(), now()->subDay()->endOfDay(), 'yesterday'],
            '30d' => [now()->subDays(29)->startOfDay(), now()->endOfDay(), '30d'],
            '90d' => [now()->subDays(89)->startOfDay(), now()->endOfDay(), '90d'],
            'custom' => [
                Carbon::parse((string) $request->query('from', now()->subDays(6)->toDateString()))->startOfDay(),
                Carbon::parse((string) $request->query('to', now()->toDateString()))->endOfDay(),
                'custom',
            ],
            default => [now()->subDays(6)->startOfDay(), now()->endOfDay(), '7d'],
        };
    }

    /**
     * @return list<array<string, string|bool>>
     */
    private function nav(): array
    {
        return [
            ['label' => 'Overview', 'href' => route('admin.academy.analytics.overview')],
            ['label' => 'Intelligence', 'href' => route('admin.academy.analytics.intelligence')],
            ['label' => 'Content', 'href' => route('admin.academy.analytics.content')],
            ['label' => 'Prompt Library', 'href' => route('admin.academy.analytics.prompt-library')],
            ['label' => 'Prompts', 'href' => route('admin.academy.analytics.prompts')],
            ['label' => 'Lessons', 'href' => route('admin.academy.analytics.lessons')],
            ['label' => 'Courses', 'href' => route('admin.academy.analytics.courses')],
            ['label' => 'Search', 'href' => route('admin.academy.analytics.search')],
            ['label' => 'Funnel', 'href' => route('admin.academy.analytics.funnel')],
        ];
    }

    /**
     * @return array<string, mixed>
     */
    private function rangePayload(string $activeRange, Carbon $from, Carbon $to): array
    {
        return [
            'active' => $activeRange,
            'from' => $from->toDateString(),
            'to' => $to->toDateString(),
            'options' => [
                ['value' => 'today', 'label' => 'Today'],
                ['value' => 'yesterday', 'label' => 'Yesterday'],
                ['value' => '7d', 'label' => 'Last 7 days'],
                ['value' => '30d', 'label' => 'Last 30 days'],
                ['value' => '90d', 'label' => 'Last 90 days'],
                ['value' => 'custom', 'label' => 'Custom range'],
            ],
        ];
    }
}
