<?php

namespace App\Http\Controllers\User;

use App\Http\Controllers\Controller;
use App\Http\Requests\ProfileUpdateRequest;
use App\Http\Requests\Settings\RequestEmailChangeRequest;
use App\Http\Requests\Settings\UpdateAccountSectionRequest;
use App\Http\Requests\Settings\UpdateContentPreferencesRequest;
use App\Http\Requests\Settings\UpdateNotificationsSectionRequest;
use App\Http\Requests\Settings\UpdatePersonalSectionRequest;
use App\Http\Requests\Settings\UpdateProfileSectionRequest;
use App\Http\Requests\Settings\UpdateSecurityPasswordRequest;
use App\Http\Requests\Settings\VerifyEmailChangeRequest;
use App\Mail\EmailChangedSecurityAlertMail;
use App\Mail\EmailChangeVerificationCodeMail;
use App\Models\Artwork;
use App\Models\Country;
use App\Models\Group;
use App\Models\GroupContributorStat;
use App\Models\GroupMember;
use App\Models\GroupProjectMember;
use App\Models\GroupRelease;
use App\Models\GroupReleaseContributor;
use App\Models\ProfileComment;
use App\Models\Story;
use App\Models\User;
use Carbon\CarbonInterface;
use App\Services\Security\CaptchaVerifier;
use App\Services\AvatarService;
use App\Services\ArtworkService;
use App\Services\FollowService;
use App\Services\AchievementService;
use App\Services\CollectionService;
use App\Services\FollowAnalyticsService;
use App\Services\LeaderboardService;
use App\Services\UserSuggestionService;
use App\Services\Countries\CountryCatalogService;
use App\Services\Maturity\ArtworkMaturityService;
use App\Services\ThumbnailPresenter;
use App\Services\XPService;
use App\Services\UsernameApprovalService;
use App\Services\UserStatsService;
use App\Support\AvatarUrl;
use App\Support\CoverUrl;
use App\Support\Seo\SeoFactory;
use App\Support\UsernamePolicy;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Schema;
use Illuminate\View\View;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Illuminate\Validation\Rules\Password as PasswordRule;
use Inertia\Inertia;

class ProfileController extends Controller
{
    private const PROFILE_TABS = [
        'posts',
        'artworks',
        'stories',
        'achievements',
        'collections',
        'about',
        'stats',
        'favourites',
        'activity',
    ];

    public function __construct(
        private readonly ArtworkService $artworkService,
        private readonly UsernameApprovalService $usernameApprovalService,
        private readonly FollowService $followService,
        private readonly UserStatsService $userStats,
        private readonly CaptchaVerifier $captchaVerifier,
        private readonly XPService $xp,
        private readonly AchievementService $achievements,
        private readonly CollectionService $collections,
        private readonly FollowAnalyticsService $followAnalytics,
        private readonly LeaderboardService $leaderboards,
        private readonly CountryCatalogService $countryCatalog,
        private readonly UserSuggestionService $userSuggestions,
    )
    {
    }

    public function showByUsername(Request $request, string $username)
    {
        $normalized = UsernamePolicy::normalize($username);
        $user = User::query()->whereRaw('LOWER(username) = ?', [$normalized])->first();

        if (! $user) {
            $redirect = DB::table('username_redirects')
                ->whereRaw('LOWER(old_username) = ?', [$normalized])
                ->value('new_username');

            if ($redirect) {
                return redirect()->route('profile.show', ['username' => strtolower((string) $redirect)], 301);
            }

            abort(404);
        }

        if ($username !== strtolower((string) $user->username)) {
            return redirect()->route('profile.show', ['username' => strtolower((string) $user->username)], 301);
        }

        $tab = $this->normalizeProfileTab($request->query('tab'));
        if ($tab !== null) {
            return $this->redirectToProfileTab($request, (string) $user->username, $tab);
        }

        return $this->renderProfilePage($request, $user, 'Profile/ProfileShow', false, 'posts');
    }

    public function showGalleryByUsername(Request $request, string $username)
    {
        $normalized = UsernamePolicy::normalize($username);
        $user = User::query()->whereRaw('LOWER(username) = ?', [$normalized])->first();

        if (! $user) {
            $redirect = DB::table('username_redirects')
                ->whereRaw('LOWER(old_username) = ?', [$normalized])
                ->value('new_username');

            if ($redirect) {
                return redirect()->route('profile.gallery', ['username' => strtolower((string) $redirect)], 301);
            }

            abort(404);
        }

        if ($username !== strtolower((string) $user->username)) {
            return redirect()->route('profile.gallery', ['username' => strtolower((string) $user->username)], 301);
        }

        return $this->renderProfilePage($request, $user, 'Profile/ProfileGallery', true);
    }

    public function showTabByUsername(Request $request, string $username, string $tab)
    {
        $normalized = UsernamePolicy::normalize($username);
        $user = User::query()->whereRaw('LOWER(username) = ?', [$normalized])->first();
        $normalizedTab = $this->normalizeProfileTab($tab);

        if ($normalizedTab === null) {
            abort(404);
        }

        if (! $user) {
            $redirect = DB::table('username_redirects')
                ->whereRaw('LOWER(old_username) = ?', [$normalized])
                ->value('new_username');

            if ($redirect) {
                return redirect()->route('profile.tab', [
                    'username' => strtolower((string) $redirect),
                    'tab' => $normalizedTab,
                ], 301);
            }

            abort(404);
        }

        if ($username !== strtolower((string) $user->username)) {
            return redirect()->route('profile.tab', [
                'username' => strtolower((string) $user->username),
                'tab' => $normalizedTab,
            ], 301);
        }

        if ($request->query->has('tab')) {
            return $this->redirectToProfileTab($request, (string) $user->username, $normalizedTab);
        }

        return $this->renderProfilePage($request, $user, 'Profile/ProfileShow', false, $normalizedTab);
    }

    public function legacyById(Request $request, int $id, ?string $username = null)
    {
        $user = User::query()->findOrFail($id);

        return redirect()->route('profile.show', ['username' => strtolower((string) $user->username)], 301);
    }

    public function legacyByUsername(Request $request, string $username)
    {
        return redirect()->route('profile.show', ['username' => UsernamePolicy::normalize($username)], 301);
    }

    /** Toggle follow/unfollow for the profile of $username (auth required). */
    public function toggleFollow(Request $request, string $username): JsonResponse
    {
        $normalized = UsernamePolicy::normalize($username);
        $target     = User::query()->whereRaw('LOWER(username) = ?', [$normalized])->firstOrFail();
        $actorId    = (int) Auth::id();

        if ($actorId === $target->id) {
            return response()->json(['error' => 'Cannot follow yourself.'], 422);
        }

        $following = $this->followService->toggle($actorId, (int) $target->id);
        $count     = $this->followService->followersCount((int) $target->id);

        return response()->json([
            'following'      => $following,
            'follower_count' => $count,
        ]);
    }

    /** Store a comment on a user profile (auth required). */
    public function storeComment(Request $request, string $username): RedirectResponse
    {
        $normalized = UsernamePolicy::normalize($username);
        $target = User::query()->whereRaw('LOWER(username) = ?', [$normalized])->firstOrFail();

        $request->validate([
            'body' => ['required', 'string', 'min:2', 'max:2000'],
        ]);

        $comment = ProfileComment::create([
            'profile_user_id' => $target->id,
            'author_user_id'  => Auth::id(),
            'body'            => $request->input('body'),
        ]);

        app(XPService::class)->awardCommentCreated((int) Auth::id(), (int) $comment->id, 'profile');

        return Redirect::route('profile.show', ['username' => strtolower((string) $target->username)])
            ->with('status', 'Comment posted!');
    }

    public function edit(Request $request): View
    {
        $user = $request->user()->loadMissing(['profile', 'country']);
        $selectedCountry = $this->countryCatalog->resolveUserCountry($user);

        return view('profile.edit', [
            'user' => $user,
            'countries' => $this->countryCatalog->profileSelectOptions(),
            'selectedCountryId' => $selectedCountry?->id,
        ]);
    }

    /**
     * Inertia-powered profile edit page (Settings/ProfileEdit).
     */
    public function editSettings(Request $request)
    {
        $user = $request->user()->loadMissing(['profile', 'country']);
        $cooldownDays = $this->usernameCooldownDays();
        $lastUsernameChangeAt = $this->lastUsernameChangeAt($user);
        $usernameCooldownRemainingDays = 0;

        if ($lastUsernameChangeAt !== null) {
            $nextAllowedChangeAt = $lastUsernameChangeAt->copy()->addDays($cooldownDays);
            if ($nextAllowedChangeAt->isFuture()) {
                $usernameCooldownRemainingDays = now()->diffInDays($nextAllowedChangeAt);
            }
        }

        // Parse birth date parts
        $birthDay = null;
        $birthMonth = null;
        $birthYear = null;

        // Merge modern user_profiles data
        $profileData = [];
        try {
            if (Schema::hasTable('user_profiles')) {
                $profile = DB::table('user_profiles')->where('user_id', $user->id)->first();
                if ($profile) {
                    $profileData = (array) $profile;
                    if (isset($profile->website)) $user->homepage = $profile->website;
                    if (isset($profile->about)) $user->about_me = $profile->about;
                    if (isset($profile->birthdate)) $user->birth = $profile->birthdate;
                    if (isset($profile->gender)) $user->gender = $profile->gender;
                    if (isset($profile->country_code)) $user->country_code = $profile->country_code;
                    if (isset($profile->signature)) $user->signature = $profile->signature;
                    if (isset($profile->description)) $user->description = $profile->description;
                    if (isset($profile->mlist)) $user->mlist = $profile->mlist;
                    if (isset($profile->friend_upload_notice)) $user->friend_upload_notice = $profile->friend_upload_notice;
                    if (isset($profile->auto_post_upload)) $user->auto_post_upload = $profile->auto_post_upload;
                }
            }
        } catch (\Throwable $e) {}

        if (!empty($user->birth)) {
            try {
                $dt = \Carbon\Carbon::parse($user->birth);
                $birthDay = $dt->format('d');
                $birthMonth = $dt->format('m');
                $birthYear = $dt->format('Y');
            } catch (\Throwable $e) {}
        }

        $selectedCountry = $this->countryCatalog->resolveUserCountry($user);
        $countries = $this->countryCatalog->profileSelectOptions();

        // Avatar URL
        $avatarHash = $profileData['avatar_hash'] ?? $user->icon ?? null;
        $avatarUrl = !empty($avatarHash)
            ? AvatarUrl::forUser((int) $user->id, $avatarHash, 256)
            : AvatarUrl::default();

        $emailNotifications = (bool) ($profileData['email_notifications'] ?? $profileData['mlist'] ?? $user->mlist ?? true);
        $uploadNotifications = (bool) ($profileData['upload_notifications'] ?? $profileData['friend_upload_notice'] ?? $user->friend_upload_notice ?? true);
        $followerNotifications = (bool) ($profileData['follower_notifications'] ?? true);
        $commentNotifications = (bool) ($profileData['comment_notifications'] ?? true);
        $newsletter = (bool) ($profileData['newsletter'] ?? $profileData['mlist'] ?? $user->mlist ?? false);
        $matureContentVisibility = (string) ($profileData['mature_content_visibility'] ?? config('maturity.viewer.default_mode', 'blur'));
        $matureContentWarningEnabled = array_key_exists('mature_content_warning_enabled', $profileData)
            ? (bool) $profileData['mature_content_warning_enabled']
            : (bool) config('maturity.viewer.default_warn_on_detail', true);

        return Inertia::render('Settings/ProfileEdit', [
            'user' => [
                'id' => $user->id,
                'username' => $user->username,
                'email' => $user->email,
                'name' => $user->name,
                'homepage' => $user->homepage ?? $user->website ?? null,
                'about_me' => $user->about_me ?? $user->about ?? null,
                'signature' => $user->signature ?? null,
                'description' => $user->description ?? null,
                'gender' => $user->gender ?? null,
                'birthday' => $user->birth ?? null,
                'country_id' => $selectedCountry?->id ?? $user->country_id ?? null,
                'country_code' => $selectedCountry?->iso2 ?? $user->country_code ?? null,
                'email_notifications' => $emailNotifications,
                'upload_notifications' => $uploadNotifications,
                'follower_notifications' => $followerNotifications,
                'comment_notifications' => $commentNotifications,
                'newsletter' => $newsletter,
                'mature_content_visibility' => $matureContentVisibility,
                'mature_content_warning_enabled' => $matureContentWarningEnabled,
                'last_username_change_at' => $user->last_username_change_at,
                'username_changed_at' => $user->username_changed_at,
            ],
            'avatarUrl' => $avatarUrl,
            'birthDay' => $birthDay,
            'birthMonth' => $birthMonth,
            'birthYear' => $birthYear,
            'usernameCooldownDays' => $cooldownDays,
            'usernameCooldownRemainingDays' => $usernameCooldownRemainingDays,
            'usernameCooldownActive' => $usernameCooldownRemainingDays > 0,
            'countries' => $countries,
            'flash' => [
                'status' => session('status'),
                'error' => session('error'),
                'botCaptchaRequired' => session('bot_captcha_required', false),
            ],
            'captcha' => $this->captchaVerifier->frontendConfig(),
        ])->rootView('settings');
    }

    public function updateProfileSection(UpdateProfileSectionRequest $request, AvatarService $avatarService): RedirectResponse|JsonResponse
    {
        $user = $request->user();
        $validated = $request->validated();

        $user->name = (string) $validated['display_name'];
        $user->save();

        $profileUpdates = [
            'website' => $validated['website'] ?? null,
            'about' => $validated['bio'] ?? null,
            'signature' => $validated['signature'] ?? null,
            'description' => $validated['description'] ?? null,
        ];

        $avatarUrl = AvatarUrl::forUser((int) $user->id, null, 256);

        if (!empty($validated['remove_avatar'])) {
            $avatarService->removeAvatar((int) $user->id);
            $avatarUrl = AvatarUrl::default();
        }

        if ($request->hasFile('avatar')) {
            $hash = $avatarService->storeFromUploadedFile(
                (int) $user->id,
                $request->file('avatar'),
                (string) ($validated['avatar_position'] ?? 'center')
            );
            $avatarUrl = AvatarUrl::forUser((int) $user->id, $hash, 256);
        }

        $this->persistProfileUpdates((int) $user->id, $profileUpdates);

        return $this->settingsResponse(
            $request,
            'Profile updated successfully.',
            ['avatarUrl' => $avatarUrl]
        );
    }

    public function updateAccountSection(UpdateAccountSectionRequest $request): RedirectResponse|JsonResponse
    {
        return $this->updateUsername($request);
    }

    public function updateUsername(UpdateAccountSectionRequest $request): RedirectResponse|JsonResponse
    {
        $user = $request->user();
        $validated = $request->validated();

        $incomingUsername = UsernamePolicy::normalize((string) $validated['username']);
        $currentUsername = UsernamePolicy::normalize((string) ($user->username ?? ''));

        if ($incomingUsername !== '' && $incomingUsername !== $currentUsername) {
            $similar = UsernamePolicy::similarReserved($incomingUsername);
            if ($similar !== null && ! UsernamePolicy::hasApprovedOverride($incomingUsername, (int) $user->id)) {
                $this->usernameApprovalService->submit($user, $incomingUsername, 'profile_update', [
                    'current_username' => $currentUsername,
                ]);

                return $this->usernameValidationError($request, 'This username is too similar to a reserved name and requires manual approval.');
            }

            $cooldownDays = $this->usernameCooldownDays();
            $isAdmin = method_exists($user, 'isAdmin') ? $user->isAdmin() : false;
            $lastUsernameChangeAt = $this->lastUsernameChangeAt($user);
            if (! $isAdmin && $lastUsernameChangeAt !== null && $lastUsernameChangeAt->gt(now()->subDays($cooldownDays))) {
                $remainingDays = now()->diffInDays($lastUsernameChangeAt->copy()->addDays($cooldownDays));

                return $this->usernameValidationError($request, "You can change your username again in {$remainingDays} days.");
            }

            $user->username = $incomingUsername;
            $user->username_changed_at = now();
            if (Schema::hasColumn('users', 'last_username_change_at')) {
                $user->last_username_change_at = now();
            }

            $this->storeUsernameHistory((int) $user->id, $currentUsername);
            $this->storeUsernameRedirect((int) $user->id, $currentUsername, $incomingUsername);
        }

        $user->save();

        return $this->settingsResponse($request, 'Account updated successfully.');
    }

    public function requestEmailChange(RequestEmailChangeRequest $request): RedirectResponse|JsonResponse
    {
        if (! Schema::hasTable('email_changes')) {
            return response()->json([
                'errors' => [
                    'new_email' => ['Email change is not available right now.'],
                ],
            ], 422);
        }

        $user = $request->user();
        $validated = $request->validated();
        $newEmail = strtolower((string) $validated['new_email']);
        $code = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);
        $expiresInMinutes = 10;

        DB::table('email_changes')->where('user_id', (int) $user->id)->delete();
        DB::table('email_changes')->insert([
            'user_id' => (int) $user->id,
            'new_email' => $newEmail,
            'verification_code' => hash('sha256', $code),
            'expires_at' => now()->addMinutes($expiresInMinutes),
            'created_at' => now(),
            'updated_at' => now(),
        ]);

        Mail::to($newEmail)->queue(new EmailChangeVerificationCodeMail($code, $expiresInMinutes));

        return $this->settingsResponse($request, 'Verification code sent to your new email address.');
    }

    public function verifyEmailChange(VerifyEmailChangeRequest $request): RedirectResponse|JsonResponse
    {
        if (! Schema::hasTable('email_changes')) {
            return response()->json([
                'errors' => [
                    'code' => ['Email change verification is not available right now.'],
                ],
            ], 422);
        }

        $user = $request->user();
        $validated = $request->validated();
        $codeHash = hash('sha256', (string) $validated['code']);

        $change = DB::table('email_changes')
            ->where('user_id', (int) $user->id)
            ->whereNull('used_at')
            ->orderByDesc('id')
            ->first();

        if (! $change) {
            return response()->json(['errors' => ['code' => ['No pending email change request found.']]], 422);
        }

        if (now()->greaterThan($change->expires_at)) {
            DB::table('email_changes')->where('id', $change->id)->delete();

            return response()->json(['errors' => ['code' => ['Verification code has expired. Please request a new one.']]], 422);
        }

        if (! hash_equals((string) $change->verification_code, $codeHash)) {
            return response()->json(['errors' => ['code' => ['Verification code is invalid.']]], 422);
        }

        $newEmail = strtolower((string) $change->new_email);
        $oldEmail = strtolower((string) ($user->email ?? ''));

        DB::transaction(function () use ($user, $change, $newEmail): void {
            $lockedUser = User::query()->whereKey((int) $user->id)->lockForUpdate()->firstOrFail();
            $lockedUser->email = $newEmail;
            $lockedUser->email_verified_at = now();
            $lockedUser->save();

            DB::table('email_changes')
                ->where('id', (int) $change->id)
                ->update([
                    'used_at' => now(),
                    'updated_at' => now(),
                ]);

            DB::table('email_changes')
                ->where('user_id', (int) $user->id)
                ->where('id', '!=', (int) $change->id)
                ->delete();
        });

        if ($oldEmail !== '' && $oldEmail !== $newEmail) {
            Mail::to($oldEmail)->queue(new EmailChangedSecurityAlertMail($newEmail));
        }

        return $this->settingsResponse($request, 'Email updated successfully.', [
            'email' => $newEmail,
        ]);
    }

    public function updatePersonalSection(UpdatePersonalSectionRequest $request): RedirectResponse|JsonResponse
    {
        $validated = $request->validated();
        $selectedCountry = $this->resolveCountrySelection($validated['country_id'] ?? null);
        $this->persistUserCountrySelection($request->user(), $selectedCountry);

        $profileUpdates = [
            'birthdate' => $validated['birthday'] ?? null,
            'country_code' => $selectedCountry?->iso2,
        ];

        if (!empty($validated['gender'])) {
            $profileUpdates['gender'] = strtoupper((string) $validated['gender']);
        }

        $this->persistProfileUpdates((int) $request->user()->id, $profileUpdates);

        return $this->settingsResponse($request, 'Personal details saved successfully.');
    }

    public function updateNotificationsSection(UpdateNotificationsSectionRequest $request): RedirectResponse|JsonResponse
    {
        $validated = $request->validated();
        $userId = (int) $request->user()->id;

        $profileUpdates = [
            'email_notifications' => (bool) $validated['email_notifications'],
            'upload_notifications' => (bool) $validated['upload_notifications'],
            'follower_notifications' => (bool) $validated['follower_notifications'],
            'comment_notifications' => (bool) $validated['comment_notifications'],
            'newsletter' => (bool) $validated['newsletter'],
            // Legacy compatibility mappings.
            'mlist' => (bool) $validated['newsletter'],
            'friend_upload_notice' => (bool) $validated['upload_notifications'],
        ];

        $this->persistProfileUpdates($userId, $profileUpdates);

        return $this->settingsResponse($request, 'Notification settings saved successfully.');
    }

    public function updateContentPreferencesSection(UpdateContentPreferencesRequest $request): RedirectResponse|JsonResponse
    {
        $validated = $request->validated();

        $this->persistProfileUpdates((int) $request->user()->id, [
            'mature_content_visibility' => (string) $validated['mature_content_visibility'],
            'mature_content_warning_enabled' => (bool) $validated['mature_content_warning_enabled'],
        ]);

        return $this->settingsResponse($request, 'Content preferences saved successfully.');
    }

    public function updateSecurityPassword(UpdateSecurityPasswordRequest $request): RedirectResponse|JsonResponse
    {
        $validated = $request->validated();
        $user = $request->user();
        $user->password = Hash::make((string) $validated['new_password']);
        $user->save();

        return $this->settingsResponse($request, 'Password updated successfully.');
    }

    private function settingsResponse(Request $request, string $message, array $payload = []): RedirectResponse|JsonResponse
    {
        if ($request->expectsJson()) {
            return response()->json([
                'success' => true,
                'message' => $message,
                ...$payload,
            ]);
        }

        return Redirect::back()->with('status', $message);
    }

    private function persistProfileUpdates(int $userId, array $updates): void
    {
        if ($updates === [] || !Schema::hasTable('user_profiles')) {
            return;
        }

        $filtered = [];
        foreach ($updates as $column => $value) {
            if (Schema::hasColumn('user_profiles', $column)) {
                $filtered[$column] = $value;
            }
        }

        if ($filtered === []) {
            return;
        }

        DB::table('user_profiles')->updateOrInsert(['user_id' => $userId], $filtered);
    }

    private function resolveCountrySelection(int|string|null $countryId = null, ?string $countryCode = null): ?Country
    {
        if (is_numeric($countryId) && (int) $countryId > 0) {
            return $this->countryCatalog->findById((int) $countryId);
        }

        if ($countryCode !== null && trim($countryCode) !== '') {
            return $this->countryCatalog->findByIso2($countryCode);
        }

        return null;
    }

    private function persistUserCountrySelection(User $user, ?Country $country): void
    {
        if (! Schema::hasColumn('users', 'country_id')) {
            return;
        }

        $user->country_id = $country?->id;
        $user->save();
    }

    private function usernameCooldownDays(): int
    {
        return max(1, (int) config('usernames.rename_cooldown_days', 30));
    }

    private function lastUsernameChangeAt(User $user): ?\Illuminate\Support\Carbon
    {
        return $user->last_username_change_at ?? $user->username_changed_at;
    }

    private function usernameValidationError(Request $request, string $message): RedirectResponse|JsonResponse
    {
        $error = ['username' => [$message]];
        if ($request->expectsJson()) {
            return response()->json(['errors' => $error], 422);
        }

        return Redirect::back()->withErrors($error);
    }

    private function storeUsernameHistory(int $userId, string $oldUsername): void
    {
        if ($oldUsername === '' || ! Schema::hasTable('username_history')) {
            return;
        }

        $payload = [
            'user_id' => $userId,
            'old_username' => $oldUsername,
            'created_at' => now(),
        ];

        if (Schema::hasColumn('username_history', 'changed_at')) {
            $payload['changed_at'] = now();
        }

        if (Schema::hasColumn('username_history', 'updated_at')) {
            $payload['updated_at'] = now();
        }

        DB::table('username_history')->insert($payload);
    }

    private function storeUsernameRedirect(int $userId, string $oldUsername, string $newUsername): void
    {
        if ($oldUsername === '' || ! Schema::hasTable('username_redirects')) {
            return;
        }

        DB::table('username_redirects')->updateOrInsert(
            ['old_username' => $oldUsername],
            [
                'new_username' => $newUsername,
                'user_id' => $userId,
                'updated_at' => now(),
                'created_at' => now(),
            ]
        );
    }

    public function update(ProfileUpdateRequest $request, \App\Services\AvatarService $avatarService): RedirectResponse|JsonResponse
    {
        $user = $request->user();

        $validated = $request->validated();

        logger()->debug('Profile update validated data', $validated);

        if (isset($validated['name'])) {
            $user->name = $validated['name'];
        }

        if (array_key_exists('username', $validated)) {
            $incomingUsername = UsernamePolicy::normalize((string) $validated['username']);
            $currentUsername = UsernamePolicy::normalize((string) ($user->username ?? ''));

            if ($incomingUsername !== '' && $incomingUsername !== $currentUsername) {
                $similar = UsernamePolicy::similarReserved($incomingUsername);
                if ($similar !== null && ! UsernamePolicy::hasApprovedOverride($incomingUsername, (int) $user->id)) {
                    $this->usernameApprovalService->submit($user, $incomingUsername, 'profile_update', [
                        'current_username' => $currentUsername,
                    ]);

                    $error = ['username' => ['This username is too similar to a reserved name and requires manual approval.']];
                    if ($request->expectsJson()) {
                        return response()->json(['errors' => $error], 422);
                    }
                    return Redirect::back()->withErrors($error);
                }

                $cooldownDays = $this->usernameCooldownDays();
                $isAdmin = method_exists($user, 'isAdmin') ? $user->isAdmin() : false;
                $lastUsernameChangeAt = $this->lastUsernameChangeAt($user);

                if (! $isAdmin && $lastUsernameChangeAt !== null && $lastUsernameChangeAt->gt(now()->subDays($cooldownDays))) {
                    $error = ['username' => ["Username can only be changed once every {$cooldownDays} days."]];
                    if ($request->expectsJson()) {
                        return response()->json(['errors' => $error], 422);
                    }
                    return Redirect::back()->withErrors($error);
                }

                $user->username = $incomingUsername;
                $user->username_changed_at = now();
                if (Schema::hasColumn('users', 'last_username_change_at')) {
                    $user->last_username_change_at = now();
                }

                $this->storeUsernameHistory((int) $user->id, $currentUsername);
                $this->storeUsernameRedirect((int) $user->id, $currentUsername, $incomingUsername);
            }
        }

        if (!empty($validated['email']) && empty($user->email)) {
            $user->email = $validated['email'];
            $user->email_verified_at = null;
        }

        $user->save();

        $profileUpdates = [];
        if (!empty($validated['about'])) $profileUpdates['about'] = $validated['about'];

        if (!empty($validated['web'])) {
            $profileUpdates['website'] = $validated['web'];
        } elseif (!empty($validated['homepage'])) {
            $profileUpdates['website'] = $validated['homepage'];
        }

        $day = $validated['day'] ?? null;
        $month = $validated['month'] ?? null;
        $year = $validated['year'] ?? null;
        if ($year && $month && $day) {
            $profileUpdates['birthdate'] = sprintf('%04d-%02d-%02d', (int)$year, (int)$month, (int)$day);
        }

        if (!empty($validated['gender'])) {
            $g = strtolower($validated['gender']);
            $map = ['m' => 'M', 'f' => 'F', 'n' => 'X', 'x' => 'X'];
            $profileUpdates['gender'] = $map[$g] ?? strtoupper($validated['gender']);
        }

        if (array_key_exists('country_id', $validated) || array_key_exists('country', $validated)) {
            $selectedCountry = $this->resolveCountrySelection(
                $validated['country_id'] ?? null,
                $validated['country'] ?? null,
            );

            $this->persistUserCountrySelection($user, $selectedCountry);
            $profileUpdates['country_code'] = $selectedCountry?->iso2;
        }

        if (array_key_exists('mailing', $validated)) {
            $profileUpdates['mlist'] = filter_var($validated['mailing'], FILTER_VALIDATE_BOOLEAN) ? 1 : 0;
        }
        if (array_key_exists('notify', $validated)) {
            $profileUpdates['friend_upload_notice'] = filter_var($validated['notify'], FILTER_VALIDATE_BOOLEAN) ? 1 : 0;
        }

        if (array_key_exists('auto_post_upload', $validated)) {
            $profileUpdates['auto_post_upload'] = filter_var($validated['auto_post_upload'], FILTER_VALIDATE_BOOLEAN) ? 1 : 0;
        }

        if (isset($validated['signature'])) $profileUpdates['signature'] = $validated['signature'];
        if (isset($validated['description'])) $profileUpdates['description'] = $validated['description'];

        if (isset($validated['about'])) $profileUpdates['about'] = $validated['about'];

        if ($request->hasFile('avatar')) {
            try {
                $avatarService->storeFromUploadedFile($user->id, $request->file('avatar'));
            } catch (\Exception $e) {
                if ($request->expectsJson()) {
                    return response()->json(['errors' => ['avatar' => ['Avatar processing failed: ' . $e->getMessage()]]], 422);
                }
                return Redirect::back()->with('error', 'Avatar processing failed: ' . $e->getMessage());
            }
        }

        if ($request->hasFile('emoticon')) {
            $file = $request->file('emoticon');
            $fname = $file->getClientOriginalName();
            $path = \Illuminate\Support\Facades\Storage::disk('public')->putFileAs('user-emoticons/'.$user->id, $file, $fname);
            try {
                \Illuminate\Support\Facades\DB::table('users')->where('id', $user->id)->update(['eicon' => $fname]);
            } catch (\Exception $e) {}
        }

        if ($request->hasFile('photo')) {
            $file = $request->file('photo');
            $fname = $file->getClientOriginalName();
            $path = \Illuminate\Support\Facades\Storage::disk('public')->putFileAs('user-picture/'.$user->id, $file, $fname);
            if (\Illuminate\Support\Facades\Schema::hasTable('user_profiles')) {
                $profileUpdates['cover_image'] = $fname;
            } else {
                try {
                    \Illuminate\Support\Facades\DB::table('users')->where('id', $user->id)->update(['picture' => $fname]);
                } catch (\Exception $e) {}
            }
        }

        try {
            if (\Illuminate\Support\Facades\Schema::hasTable('user_profiles')) {
                if (!empty($profileUpdates)) {
                    \Illuminate\Support\Facades\DB::table('user_profiles')->updateOrInsert(['user_id' => $user->id], $profileUpdates);
                }
            } else {
                if (!empty($profileUpdates)) {
                    \Illuminate\Support\Facades\DB::table('users')->where('id', $user->id)->update($profileUpdates);
                }
            }
        } catch (\Exception $e) {
            logger()->error('Profile update error: '.$e->getMessage());
        }

        if ($request->expectsJson()) {
            return response()->json(['success' => true]);
        }

        return Redirect::route('dashboard.profile')->with('status', 'profile-updated');
    }

    public function destroy(Request $request): RedirectResponse|JsonResponse
    {
        $bag = $request->expectsJson() ? 'default' : 'userDeletion';
        $request->validateWithBag($bag, [
            'password' => ['required', 'current_password'],
        ]);

        $user = $request->user();

        Auth::logout();

        $user->delete();

        $request->session()->invalidate();
        $request->session()->regenerateToken();

        if ($request->expectsJson()) {
            return response()->json(['success' => true]);
        }

        return Redirect::to('/');
    }

    public function password(Request $request): RedirectResponse|JsonResponse
    {
        $request->validate([
            'current_password' => ['required', 'current_password'],
            'password' => ['required', 'confirmed', PasswordRule::min(8)],
        ]);

        $user = $request->user();
        $user->password = Hash::make($request->input('password'));
        $user->save();

        if ($request->expectsJson()) {
            return response()->json(['success' => true]);
        }

        return Redirect::route('dashboard.profile')->with('status', 'password-updated');
    }

    private function renderProfilePage(
        Request $request,
        User $user,
        string $component = 'Profile/ProfileShow',
        bool $galleryOnly = false,
        ?string $initialTab = null,
    )
    {
        $isOwner  = Auth::check() && Auth::id() === $user->id;
        $viewer   = Auth::user();
        $perPage  = 24;

        // ── Artworks (cursor-paginated) ──────────────────────────────────────
        $artworks = $this->artworkService->getArtworksByUser($user->id, $isOwner, $perPage, $viewer)
            ->through(function (Artwork $art) {
                return (object) $this->mapArtworkCardPayload($art);
            });

        // ── Featured artworks for this user ─────────────────────────────────
        $featuredArtworks = collect();
        if (Schema::hasTable('artwork_features')) {
            $featuredQuery = Artwork::query()
                ->with([
                    'user:id,name,username,level,rank',
                    'user.profile:user_id,avatar_hash',
                    'group:id,name,slug,avatar_path',
                    'stats:artwork_id,views,downloads,favorites',
                    'categories' => function ($query) {
                        $query->select('categories.id', 'categories.content_type_id', 'categories.parent_id', 'categories.name', 'categories.slug', 'categories.sort_order')
                            ->with(['parent:id,parent_id,content_type_id,name,slug', 'contentType:id,slug,name']);
                    },
                ])
                ->join('artwork_features as af', 'af.artwork_id', '=', 'artworks.id')
                ->where('artworks.user_id', $user->id)
                ->where('af.is_active', true)
                ->whereNull('af.deleted_at')
                ->whereNull('artworks.deleted_at')
                ->where('artworks.is_public', true)
                ->where('artworks.is_approved', true)
                ->whereNotNull('artworks.published_at')
                ->select(['artworks.*', 'af.label as featured_label', 'af.featured_at as featured_slot_at'])
                ->orderByDesc('af.featured_at')
                ->limit(3);

            app(ArtworkMaturityService::class)->applyViewerFilter($featuredQuery, $viewer);

            $featuredArtworks = $featuredQuery
                ->get()
                ->map(function (Artwork $artwork) {
                    return (object) array_merge($this->mapArtworkCardPayload($artwork), [
                        'label' => $artwork->featured_label,
                        'featured_at' => $this->formatIsoDate($artwork->featured_slot_at),
                    ]);
                });
        }

        // ── Favourites ───────────────────────────────────────────────────────
        $favouriteLimit = 12;
        $favouriteTable = $this->resolveFavouriteTable();
        $favourites = [
            'data' => [],
            'next_cursor' => null,
        ];
        if ($favouriteTable !== null) {
            $favIds = DB::table($favouriteTable . ' as af')
                ->join('artworks as a', 'a.id', '=', 'af.artwork_id')
                ->where('af.user_id', $user->id)
                ->whereNull('a.deleted_at')
                ->where('a.is_public', true)
                ->where('a.is_approved', true)
                ->whereNotNull('a.published_at')
                ->when(app(ArtworkMaturityService::class)->viewerPreferences($viewer)['visibility'] === ArtworkMaturityService::VIEW_HIDE, function ($query): void {
                    $query->whereRaw('COALESCE(a.is_mature, 0) = 0')
                        ->whereRaw("COALESCE(a.maturity_status, 'clear') != ?", [ArtworkMaturityService::STATUS_SUSPECTED]);
                })
                ->orderByDesc('af.created_at')
                ->orderByDesc('af.artwork_id')
                ->limit($favouriteLimit + 1)
                ->pluck('a.id');

            if ($favIds->isNotEmpty()) {
                $hasMore = $favIds->count() > $favouriteLimit;
                $favIds = $favIds->take($favouriteLimit);

                $indexed = Artwork::with([
                        'user:id,name,username,level,rank',
                        'user.profile:user_id,avatar_hash',
                        'group:id,name,slug,avatar_path',
                        'stats:artwork_id,views,downloads,favorites',
                        'categories' => function ($query) {
                            $query->select('categories.id', 'categories.content_type_id', 'categories.parent_id', 'categories.name', 'categories.slug', 'categories.sort_order')
                                ->with(['contentType:id,slug,name']);
                        },
                    ])
                    ->whereIn('id', $favIds)
                    ->get()
                    ->keyBy('id');

                $favourites = [
                    'data' => $favIds
                    ->filter(fn ($id) => $indexed->has($id))
                    ->map(fn ($id) => $this->mapArtworkCardPayload($indexed[$id]))
                    ->values()
                    ->all(),
                    'next_cursor' => $hasMore ? base64_encode((string) $favouriteLimit) : null,
                ];
            }
        }

        // ── Statistics ───────────────────────────────────────────────────────
        $stats = null;
        if (Schema::hasTable('user_statistics')) {
            $stats = DB::table('user_statistics')->where('user_id', $user->id)->first();
        }

        // ── Social links ─────────────────────────────────────────────────────
        $socialLinks = collect();
        if (Schema::hasTable('user_social_links')) {
            $socialLinks = DB::table('user_social_links')
                ->where('user_id', $user->id)
                ->get()
                ->keyBy('platform');
        }

        // ── Follower data ────────────────────────────────────────────────────
        $followerCount    = 0;
        $recentFollowers  = collect();
        $viewerIsFollowing = false;
        $followingCount = 0;

        if (Schema::hasTable('user_followers')) {
            $followerCount = DB::table('user_followers')->where('user_id', $user->id)->count();
            $followingCount = DB::table('user_followers')->where('follower_id', $user->id)->count();

            $recentFollowers = DB::table('user_followers as uf')
                ->join('users as u', 'u.id', '=', 'uf.follower_id')
                ->leftJoin('user_profiles as up', 'up.user_id', '=', 'u.id')
                ->where('uf.user_id', $user->id)
                ->whereNull('u.deleted_at')
                ->orderByDesc('uf.created_at')
                ->limit(10)
                ->select(['u.id', 'u.username', 'u.name', 'up.avatar_hash', 'uf.created_at as followed_at'])
                ->get()
                ->map(fn ($row) => (object) [
                    'id'          => $row->id,
                    'username'    => $row->username,
                    'uname'       => $row->username ?? $row->name,
                    'avatar_url'  => AvatarUrl::forUser((int) $row->id, $row->avatar_hash, 50),
                    'profile_url' => '/@' . strtolower((string) ($row->username ?? $row->id)),
                    'followed_at' => $row->followed_at,
                ]);

            if ($viewer && $viewer->id !== $user->id) {
                $viewerIsFollowing = DB::table('user_followers')
                    ->where('user_id', $user->id)
                    ->where('follower_id', $viewer->id)
                    ->exists();
            }
        }

        $liveUploadsCount = 0;
        if (Schema::hasTable('artworks')) {
            $liveUploadsCount = (int) DB::table('artworks')
                ->where('user_id', $user->id)
                ->whereNull('deleted_at')
                ->count();
        }

        $medalTotals = [
            'gold' => 0,
            'silver' => 0,
            'bronze' => 0,
            'count' => 0,
            'score_total' => 0,
        ];

        if (Schema::hasTable('artwork_medal_stats') && Schema::hasTable('artworks')) {
            $totals = DB::table('artwork_medal_stats as aas')
                ->join('artworks as a', 'a.id', '=', 'aas.artwork_id')
                ->where('a.user_id', $user->id)
                ->whereNull('a.deleted_at')
                ->selectRaw('COALESCE(SUM(aas.gold_count), 0) as gold_count')
                ->selectRaw('COALESCE(SUM(aas.silver_count), 0) as silver_count')
                ->selectRaw('COALESCE(SUM(aas.bronze_count), 0) as bronze_count')
                ->selectRaw('COALESCE(SUM(aas.score_total), 0) as score_total')
                ->first();

            $medalTotals = [
                'gold' => (int) ($totals->gold_count ?? 0),
                'silver' => (int) ($totals->silver_count ?? 0),
                'bronze' => (int) ($totals->bronze_count ?? 0),
                'count' => (int) (($totals->gold_count ?? 0) + ($totals->silver_count ?? 0) + ($totals->bronze_count ?? 0)),
                'score_total' => (int) ($totals->score_total ?? 0),
            ];
        }

        $statsPayload = array_merge($stats ? (array) $stats : [], [
            'uploads_count' => $liveUploadsCount,
            'awards_received_count' => $medalTotals['count'],
            'medal_totals' => $medalTotals,
            'followers_count' => (int) $followerCount,
            'following_count' => (int) $followingCount,
        ]);

        // ── Profile comments ─────────────────────────────────────────────────
        $profileComments = collect();
        if (Schema::hasTable('profile_comments')) {
            $profileComments = DB::table('profile_comments as pc')
                ->join('users as u', 'u.id', '=', 'pc.author_user_id')
                ->leftJoin('user_profiles as up', 'up.user_id', '=', 'u.id')
                ->where('pc.profile_user_id', $user->id)
                ->where('pc.is_active', true)
                ->whereNull('u.deleted_at')
                ->orderByDesc('pc.created_at')
                ->limit(10)
                ->select([
                    'pc.id', 'pc.body', 'pc.created_at',
                    'u.id as author_id', 'u.username as author_username', 'u.name as author_name',
                    'u.level as author_level', 'u.rank as author_rank',
                    'up.avatar_hash as author_avatar_hash', 'up.signature as author_signature',
                ])
                ->get()
                ->map(fn ($row) => (object) [
                    'id'          => $row->id,
                    'body'        => $row->body,
                    'created_at'  => $row->created_at,
                    'author_id'   => $row->author_id,
                    'author_name' => $row->author_username ?? $row->author_name ?? 'Unknown',
                    'author_level' => (int) ($row->author_level ?? 1),
                    'author_rank' => (string) ($row->author_rank ?? 'Newbie'),
                    'author_profile_url' => '/@' . strtolower((string) ($row->author_username ?? $row->author_id)),
                    'author_avatar' => AvatarUrl::forUser((int) $row->author_id, $row->author_avatar_hash, 50),
                    'author_signature' => $row->author_signature,
                ]);
        }

        $xpSummary = $this->xp->summary((int) $user->id);
        $followContext = $viewer && $viewer->id !== $user->id
            ? $this->followService->relationshipContext((int) $viewer->id, (int) $user->id)
            : null;
        $followAnalytics = $this->followAnalytics->summaryForUser((int) $user->id, $followerCount);
        $suggestedUsers = $viewer ? $this->userSuggestions->suggestFor($viewer, 4) : [];

        $creatorStories = Story::query()
            ->published()
            ->with(['tags'])
            ->where('creator_id', $user->id)
            ->latest('published_at')
            ->limit(6)
            ->get([
                'id',
                'slug',
                'title',
                'excerpt',
                'cover_image',
                'reading_time',
                'views',
                'likes_count',
                'comments_count',
                'published_at',
            ])
            ->map(fn (Story $story) => [
                'id' => $story->id,
                'slug' => $story->slug,
                'title' => $story->title,
                'excerpt' => $story->excerpt,
                'cover_url' => $story->cover_url,
                'reading_time' => $story->reading_time,
                'views' => (int) $story->views,
                'likes_count' => (int) $story->likes_count,
                'comments_count' => (int) $story->comments_count,
                'creator_level' => $xpSummary['level'],
                'creator_rank' => $xpSummary['rank'],
                'published_at' => $story->published_at?->toISOString(),
            ]);

            $profileCollections = $this->collections->getProfileCollections($user, $viewer);
            $profileCollectionsPayload = $this->collections->mapCollectionCardPayloads($profileCollections, $isOwner, $viewer);

        // ── Profile data ─────────────────────────────────────────────────────
        $profile = $user->profile;
        $country = $this->countryCatalog->resolveUserCountry($user);
        $countryCode = $country?->iso2 ?? $profile?->country_code;
        $countryName = $country?->name_common;

        if ($countryName === null && $profile?->country_code) {
            $countryName = strtoupper((string) $profile->country_code);
        }

        // ── Cover image hero (preferred) ────────────────────────────────────
        $heroBgUrl = CoverUrl::forUser($user->cover_hash, $user->cover_ext, $user->updated_at?->timestamp ?? time());

        // ── Increment profile views (async-safe, ignore errors) ──────────────
        if (! $isOwner) {
            try {
                $this->userStats->incrementProfileViews($user->id);
            } catch (\Throwable) {}
        }

        // ── Normalise artworks for JSON serialisation ────────────────────
        $artworkItems = collect($artworks->items())->values();
        $artworkPayload = [
            'data'        => $artworkItems,
            'next_cursor' => $artworks->nextCursor()?->encode(),
            'has_more'    => $artworks->hasMorePages(),
        ];

        // ── Avatar URL on user object ────────────────────────────────────
        $avatarUrl = AvatarUrl::forUser((int) $user->id, $user->profile?->avatar_hash, 128);

        // ── Auth context for JS ───────────────────────────────────────────
        $authData = null;
        if (Auth::check()) {
            /** @var \App\Models\User $authUser */
            $authUser       = Auth::user();
            $authAvatarUrl  = AvatarUrl::forUser((int) $authUser->id, $authUser->profile?->avatar_hash, 64);
            $authData = [
                'user' => [
                    'id'       => $authUser->id,
                    'username' => $authUser->username,
                    'name'     => $authUser->name,
                    'avatar'   => $authAvatarUrl,
                ],
            ];
        }

        $usernameSlug = strtolower((string) ($user->username ?? ''));
        $canonical = url('/@' . $usernameSlug);
        $galleryUrl = url('/@' . $usernameSlug . '/gallery');
        $profileTabUrls = collect(self::PROFILE_TABS)
            ->mapWithKeys(fn (string $tab) => [$tab => url('/@' . $usernameSlug . '/' . $tab)])
            ->all();
        $achievementSummary = $this->achievements->summary((int) $user->id);
        $leaderboardRank = $this->leaderboards->creatorRankSummary((int) $user->id);
        $groupContributionHistory = $this->buildGroupContributionHistory($user);
        $resolvedInitialTab = $this->normalizeProfileTab($initialTab);
        $isTabLanding = ! $galleryOnly && $resolvedInitialTab !== null;
        $activeProfileUrl = $resolvedInitialTab !== null
            ? ($profileTabUrls[$resolvedInitialTab] ?? $canonical)
            : $canonical;
        $tabMetaLabel = $resolvedInitialTab !== null
            ? ucfirst($resolvedInitialTab)
            : null;

        $pageTitle = $galleryOnly
            ? (($user->username ?? $user->name ?? 'User') . ' Gallery on Skinbase')
            : ($isTabLanding
                ? (($user->username ?? $user->name ?? 'User') . ' ' . $tabMetaLabel . ' on Skinbase')
                : (($user->username ?? $user->name ?? 'User') . ' on Skinbase'));
        $pageDescription = $galleryOnly
            ? ('Browse the public gallery of ' . ($user->username ?? $user->name) . ' on Skinbase.')
            : ($isTabLanding
                ? ('Explore the ' . strtolower((string) $tabMetaLabel) . ' section for ' . ($user->username ?? $user->name) . ' on Skinbase.')
                : ('View the profile of ' . ($user->username ?? $user->name) . ' on Skinbase — artworks, favourites and more.'));
        $profileSeo = app(SeoFactory::class)->profilePage(
            $pageTitle,
            $galleryOnly ? $galleryUrl : $activeProfileUrl,
            $pageDescription,
            $avatarUrl,
            collect([
                (object) ['name' => 'Home', 'url' => '/'],
                (object) ['name' => $user->username ?? $user->name ?? 'Profile', 'url' => $galleryOnly ? $galleryUrl : $activeProfileUrl],
            ]),
        )->toArray();

        return Inertia::render($component, [
            'user'              => [
                'id'            => $user->id,
                'username'      => $user->username,
                'name'          => $user->name,
                'avatar_url'    => $avatarUrl,
                'cover_url'     => $heroBgUrl,
                'cover_position'=> (int) ($user->cover_position ?? 50),
                'created_at'    => $user->created_at?->toISOString(),
                'last_visit_at' => $user->last_visit_at ? (string) $user->last_visit_at : null,
                'xp'            => $xpSummary['xp'],
                'level'         => $xpSummary['level'],
                'rank'          => $xpSummary['rank'],
                'next_level_xp' => $xpSummary['next_level_xp'],
                'current_level_xp' => $xpSummary['current_level_xp'],
                'progress_percent' => $xpSummary['progress_percent'],
                'max_level' => $xpSummary['max_level'],
            ],
            'profile'           => $profile ? [
                'about'        => $profile->about ?? null,
                'website'      => $profile->website ?? null,
                'country_code' => $countryCode,
                'gender'       => $profile->gender ?? null,
                'birthdate'    => $profile->birthdate ?? null,
                'cover_image'  => $profile->cover_image ?? null,
            ] : null,
            'artworks'          => $artworkPayload,
            'featuredArtworks'  => $featuredArtworks->values(),
            'favourites'        => $favourites,
            'stats'             => $statsPayload,
            'socialLinks'       => $socialLinks,
            'followerCount'     => $followerCount,
            'recentFollowers'   => $recentFollowers->values(),
            'followContext'     => $followContext,
            'followAnalytics'   => $followAnalytics,
            'suggestedUsers'    => $suggestedUsers,
            'viewerIsFollowing' => $viewerIsFollowing,
            'heroBgUrl'         => $heroBgUrl,
            'profileComments'   => $profileComments->values(),
            'creatorStories'    => $creatorStories->values(),
            'collections'       => $profileCollectionsPayload,
            'achievements'      => $achievementSummary,
            'leaderboardRank'   => $leaderboardRank,
            'groupContributionHistory' => $groupContributionHistory,
            'countryName'       => $countryName,
            'isOwner'           => $isOwner,
            'auth'              => $authData,
            'initialTab'        => $resolvedInitialTab,
            'profileUrl'        => $canonical,
            'galleryUrl'        => $galleryUrl,
            'collectionCreateUrl' => $isOwner ? route('settings.collections.create') : null,
            'collectionReorderUrl' => $isOwner ? route('settings.collections.reorder-profile') : null,
            'collectionsFeaturedUrl' => route('collections.featured'),
            'collectionFeatureLimit' => (int) config('collections.featured_limit', 3),
            'profileTabUrls'    => $profileTabUrls,
        ])->withViewData([
            'page_title'            => $pageTitle,
            'page_canonical'        => $galleryOnly ? $galleryUrl : $activeProfileUrl,
            'page_meta_description' => $pageDescription,
            'og_image'              => $avatarUrl,
            'seo'                   => $profileSeo,
            'useUnifiedSeo'         => true,
        ]);
    }

    private function normalizeProfileTab(mixed $tab): ?string
    {
        if (! is_string($tab)) {
            return null;
        }

        $normalized = strtolower(trim($tab));

        return in_array($normalized, self::PROFILE_TABS, true) ? $normalized : null;
    }

    private function redirectToProfileTab(Request $request, string $username, string $tab): RedirectResponse
    {
        $baseUrl = url('/@' . strtolower($username) . '/' . $tab);

        $query = $request->query();
        unset($query['tab']);

        if ($query !== []) {
            $baseUrl .= '?' . http_build_query($query);
        }

        return redirect()->to($baseUrl, 301);
    }

    private function buildGroupContributionHistory(User $user): array
    {
        return GroupContributorStat::query()
            ->with(['group.owner.profile'])
            ->where('user_id', $user->id)
            ->whereHas('group', fn ($query) => $query
                ->whereIn('visibility', [Group::VISIBILITY_PUBLIC, Group::VISIBILITY_UNLISTED])
                ->where('status', '!=', Group::LIFECYCLE_SUSPENDED))
            ->orderByDesc('release_count')
            ->orderByDesc('credited_artworks_count')
            ->limit(8)
            ->get()
            ->map(function (GroupContributorStat $stat) use ($user): ?array {
                $group = $stat->group;

                if (! $group) {
                    return null;
                }

                $member = GroupMember::query()
                    ->where('group_id', $group->id)
                    ->where('user_id', $user->id)
                    ->where('status', Group::STATUS_ACTIVE)
                    ->first();

                $roleLabels = collect()
                    ->merge(GroupReleaseContributor::query()
                        ->where('user_id', $user->id)
                        ->whereHas('release', fn ($query) => $query->where('group_id', $group->id))
                        ->pluck('role_label'))
                    ->merge(GroupProjectMember::query()
                        ->where('user_id', $user->id)
                        ->whereHas('project', fn ($query) => $query->where('group_id', $group->id))
                        ->pluck('role_label'))
                    ->filter()
                    ->map(fn ($label): string => trim((string) $label))
                    ->filter()
                    ->unique()
                    ->values()
                    ->all();

                $recentReleaseTitles = GroupRelease::query()
                    ->where('group_id', $group->id)
                    ->where('visibility', GroupRelease::VISIBILITY_PUBLIC)
                    ->where(function ($query) use ($user): void {
                        $query->where('lead_user_id', $user->id)
                            ->orWhere('created_by_user_id', $user->id)
                            ->orWhereHas('contributorLinks', fn ($contributorQuery) => $contributorQuery->where('user_id', $user->id));
                    })
                    ->orderByDesc('released_at')
                    ->latest('updated_at')
                    ->limit(3)
                    ->pluck('title')
                    ->map(fn ($title): string => (string) $title)
                    ->values()
                    ->all();

                $joinedAt = $group->isOwnedBy($user)
                    ? $group->created_at?->toISOString()
                    : ($member?->accepted_at?->toISOString() ?? $member?->created_at?->toISOString());
                $meta = is_array($stat->reputation_meta_json) ? $stat->reputation_meta_json : [];

                return [
                    'group' => [
                        'id' => (int) $group->id,
                        'name' => (string) $group->name,
                        'slug' => (string) $group->slug,
                        'headline' => $group->headline,
                        'avatar_url' => $group->avatarUrl(),
                        'profile_url' => $group->publicUrl(),
                    ],
                    'joined_at' => $joinedAt,
                    'role' => $group->isOwnedBy($user)
                        ? Group::ROLE_OWNER
                        : Group::displayRole($member?->role ?? Group::ROLE_MEMBER),
                    'counts' => [
                        'credited_artworks' => (int) $stat->credited_artworks_count,
                        'releases' => (int) $stat->release_count,
                        'projects' => (int) $stat->project_count,
                        'review_actions' => (int) $stat->review_actions_count,
                    ],
                    'trusted_indicator' => (bool) ($meta['trusted_indicator'] ?? false),
                    'summary' => $meta['summary'] ?? null,
                    'role_labels' => $roleLabels,
                    'recent_release_titles' => $recentReleaseTitles,
                ];
            })
            ->filter()
            ->values()
            ->all();
    }

    private function resolveFavouriteTable(): ?string
    {
        foreach (['artwork_favourites', 'user_favorites', 'artworks_favourites', 'favourites'] as $table) {
            if (Schema::hasTable($table)) {
                return $table;
            }
        }

        return null;
    }

    /**
     * @return array<string, mixed>
     */
    private function mapArtworkCardPayload(Artwork $art): array
    {
        $present = ThumbnailPresenter::present($art, 'md');
        $category = $art->categories->first();
        $contentType = $category?->contentType;
        $stats = $art->stats;
        $group = $art->group;
        $isGroupPublisher = $group !== null;
        $displayName = $isGroupPublisher ? ($group->name ?? 'Skinbase') : ($art->user?->name ?? 'Skinbase');
        $username = $isGroupPublisher ? null : ($art->user?->username ?? null);
        $avatarUrl = $isGroupPublisher ? $group->avatarUrl() : ($art->user?->profile?->avatar_url ?? null);
        $profileUrl = $isGroupPublisher
            ? $group->publicUrl()
            : ($username ? '/@' . $username : null);
        $publisherType = $isGroupPublisher ? 'group' : 'user';

        return app(ArtworkMaturityService::class)->decoratePayload([
            'id' => $art->id,
            'name' => $art->title,
            'picture' => $art->file_name,
            'datum' => $this->formatIsoDate($art->published_at),
            'published_at' => $this->formatIsoDate($art->published_at),
            'thumb' => $present['url'],
            'thumb_srcset' => $present['srcset'] ?? $present['url'],
            'uname' => $displayName,
            'username' => $username,
            'avatar_url' => $avatarUrl,
            'profile_url' => $profileUrl,
            'published_as_type' => $publisherType,
            'publisher' => [
                'type' => $publisherType,
                'id' => $isGroupPublisher ? (int) $group->id : (int) ($art->user?->id ?? 0),
                'name' => $displayName,
                'username' => $username ?? '',
                'avatar_url' => $avatarUrl,
                'profile_url' => $profileUrl,
            ],
            'user_id' => $art->user_id,
            'author_level' => $isGroupPublisher ? 0 : (int) ($art->user?->level ?? 1),
            'author_rank' => $isGroupPublisher ? '' : (string) ($art->user?->rank ?? 'Newbie'),
            'content_type' => $contentType?->name,
            'content_type_slug' => $contentType?->slug,
            'category' => $category?->name,
            'category_slug' => $category?->slug,
            'views' => (int) ($stats?->views ?? $art->view_count ?? 0),
            'downloads' => (int) ($stats?->downloads ?? 0),
            'likes' => (int) ($stats?->favorites ?? $art->favourite_count ?? 0),
            'width' => $art->width,
            'height' => $art->height,
        ], $art, request()->user());
    }

    private function formatIsoDate(mixed $value): ?string
    {
        if ($value instanceof CarbonInterface) {
            return $value->toISOString();
        }

        if ($value instanceof \DateTimeInterface) {
            return $value->format(DATE_ATOM);
        }

        return is_string($value) ? $value : null;
    }
}
