<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Group;
use App\Models\GroupInvitation;
use App\Models\GroupMember;
use App\Models\User;
use App\Support\AvatarUrl;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class GroupMembershipService
{
    public function __construct(
        private readonly NotificationService $notifications,
    ) {
    }

    public function ensureOwnerMembership(Group $group): void
    {
        GroupMember::query()
            ->where('group_id', $group->id)
            ->where('role', Group::ROLE_OWNER)
            ->where('user_id', '!=', $group->owner_user_id)
            ->update([
                'role' => Group::ROLE_ADMIN,
                'updated_at' => now(),
            ]);

        GroupMember::query()->updateOrCreate(
            [
                'group_id' => $group->id,
                'user_id' => $group->owner_user_id,
            ],
            [
                'invited_by_user_id' => $group->owner_user_id,
                'role' => Group::ROLE_OWNER,
                'status' => Group::STATUS_ACTIVE,
                'invited_at' => now(),
                'expires_at' => null,
                'accepted_at' => now(),
                'revoked_at' => null,
            ]
        );
    }

    public function inviteMember(Group $group, User $actor, User $invitee, string $role, ?string $note = null, ?int $expiresInDays = null): GroupInvitation
    {
        $this->guardManageMembers($group, $actor);
        $this->expirePendingInvites();
        $role = Group::normalizeMemberRole($role);

        if (! in_array($role, [Group::ROLE_ADMIN, Group::ROLE_EDITOR, Group::ROLE_MEMBER], true)) {
            throw ValidationException::withMessages([
                'role' => 'Choose a valid group role.',
            ]);
        }

        if ($group->isOwnedBy($invitee)) {
            throw ValidationException::withMessages([
                'username' => 'The group owner is already a member.',
            ]);
        }

        $existingMembership = GroupMember::query()
            ->where('group_id', $group->id)
            ->where('user_id', $invitee->id)
            ->where('status', Group::STATUS_ACTIVE)
            ->exists();

        if ($existingMembership) {
            throw ValidationException::withMessages([
                'username' => 'This user is already an active member of the group.',
            ]);
        }

        $invitation = DB::transaction(function () use ($group, $actor, $invitee, $role, $note, $expiresInDays): GroupInvitation {
            $now = now();

            GroupInvitation::query()
                ->where('group_id', $group->id)
                ->where('invited_user_id', $invitee->id)
                ->where('status', GroupInvitation::STATUS_PENDING)
                ->update([
                    'status' => GroupInvitation::STATUS_REVOKED,
                    'responded_at' => $now,
                    'revoked_at' => $now,
                    'updated_at' => $now,
                ]);

            $invitation = GroupInvitation::query()->create([
                'group_id' => $group->id,
                'invited_user_id' => $invitee->id,
                'invited_by_user_id' => $actor->id,
                'role' => $role,
                'status' => GroupInvitation::STATUS_PENDING,
                'token' => Str::random(64),
                'note' => $note,
                'invited_at' => $now,
                'expires_at' => $now->copy()->addDays(max(1, (int) ($expiresInDays ?? config('groups.invites.expires_after_days', 7)))),
                'responded_at' => null,
                'accepted_at' => null,
                'revoked_at' => null,
            ]);

            return $invitation->fresh(['group.owner.profile', 'invitedUser.profile', 'invitedBy.profile']);
        });

        $this->notifications->notifyGroupInvite($invitee, $actor, $group, $role, $invitation);

        return $invitation;
    }

    public function acceptInvitation(GroupInvitation $invitation, User $user): GroupMember
    {
        $this->expireInvitationIfNeeded($invitation);

        if ((int) $invitation->invited_user_id !== (int) $user->id || $invitation->status !== GroupInvitation::STATUS_PENDING) {
            throw ValidationException::withMessages([
                'invitation' => 'This invitation cannot be accepted.',
            ]);
        }

        $member = DB::transaction(function () use ($invitation): GroupMember {
            $acceptedAt = now();

            $member = GroupMember::query()->updateOrCreate(
                [
                    'group_id' => $invitation->group_id,
                    'user_id' => $invitation->invited_user_id,
                ],
                [
                    'invited_by_user_id' => $invitation->invited_by_user_id,
                    'role' => $invitation->role,
                    'status' => Group::STATUS_ACTIVE,
                    'note' => $invitation->note,
                    'invited_at' => $invitation->invited_at ?? $acceptedAt,
                    'expires_at' => null,
                    'accepted_at' => $acceptedAt,
                    'revoked_at' => null,
                ]
            );

            $invitation->forceFill([
                'status' => GroupInvitation::STATUS_ACCEPTED,
                'responded_at' => $acceptedAt,
                'accepted_at' => $acceptedAt,
                'revoked_at' => null,
            ])->save();

            $this->syncLegacyMemberFromInvitation($invitation, Group::STATUS_ACTIVE, $acceptedAt);

            GroupInvitation::query()
                ->where('group_id', $invitation->group_id)
                ->where('invited_user_id', $invitation->invited_user_id)
                ->where('status', GroupInvitation::STATUS_PENDING)
                ->where('id', '!=', $invitation->id)
                ->update([
                    'status' => GroupInvitation::STATUS_REVOKED,
                    'responded_at' => $acceptedAt,
                    'revoked_at' => $acceptedAt,
                    'updated_at' => $acceptedAt,
                ]);

            return $member->fresh(['group.owner.profile', 'user.profile', 'invitedBy.profile']);
        });

        $recipient = $member->invitedBy ?: $member->group?->owner;
        if ($recipient) {
            $this->notifications->notifyGroupInviteAccepted($recipient, $user, $member->group);
        }

        if ($member->group) {
            app(GroupActivityService::class)->record(
                $member->group,
                $user,
                'member_joined',
                'group_member',
                (int) $member->id,
                sprintf('%s joined %s', $user->name ?: $user->username ?: 'A member', $member->group->name),
                'Accepted a group invitation.',
                'public',
            );
        }

        return $member;
    }

    public function declineInvitation(GroupInvitation $invitation, User $user): GroupInvitation
    {
        $this->expireInvitationIfNeeded($invitation);

        if ((int) $invitation->invited_user_id !== (int) $user->id || $invitation->status !== GroupInvitation::STATUS_PENDING) {
            throw ValidationException::withMessages([
                'invitation' => 'This invitation cannot be declined.',
            ]);
        }

        $declinedAt = now();

        $invitation->forceFill([
            'status' => GroupInvitation::STATUS_DECLINED,
            'responded_at' => $declinedAt,
            'accepted_at' => null,
            'revoked_at' => null,
        ])->save();

        $this->syncLegacyMemberFromInvitation($invitation, Group::STATUS_REVOKED, $declinedAt);

        return $invitation->fresh(['group.owner.profile', 'invitedUser.profile', 'invitedBy.profile']);
    }

    public function acceptLegacyInvite(GroupMember $member, User $user): GroupMember
    {
        $this->expireMemberIfNeeded($member);

        if ((int) $member->user_id !== (int) $user->id || $member->status !== Group::STATUS_PENDING) {
            throw ValidationException::withMessages([
                'member' => 'This invitation cannot be accepted.',
            ]);
        }

        $acceptedAt = now();

        $member->forceFill([
            'status' => Group::STATUS_ACTIVE,
            'expires_at' => null,
            'accepted_at' => $acceptedAt,
            'revoked_at' => null,
        ])->save();

        GroupInvitation::query()
            ->where('source_group_member_id', $member->id)
            ->where('status', GroupInvitation::STATUS_PENDING)
            ->update([
                'status' => GroupInvitation::STATUS_ACCEPTED,
                'responded_at' => $acceptedAt,
                'accepted_at' => $acceptedAt,
                'revoked_at' => null,
                'updated_at' => $acceptedAt,
            ]);

        $recipient = $member->invitedBy ?: $member->group?->owner;
        if ($recipient) {
            $this->notifications->notifyGroupInviteAccepted($recipient, $user, $member->group);
        }

        if ($member->group) {
            app(GroupActivityService::class)->record(
                $member->group,
                $user,
                'member_joined',
                'group_member',
                (int) $member->id,
                sprintf('%s joined %s', $user->name ?: $user->username ?: 'A member', $member->group->name),
                'Accepted a legacy group invitation.',
                'public',
            );
        }

        return $member->fresh(['group.owner.profile', 'user.profile', 'invitedBy.profile']);
    }

    public function declineLegacyInvite(GroupMember $member, User $user): GroupMember
    {
        $this->expireMemberIfNeeded($member);

        if ((int) $member->user_id !== (int) $user->id || $member->status !== Group::STATUS_PENDING) {
            throw ValidationException::withMessages([
                'member' => 'This invitation cannot be declined.',
            ]);
        }

        $declinedAt = now();

        $member->forceFill([
            'status' => Group::STATUS_REVOKED,
            'accepted_at' => null,
            'revoked_at' => $declinedAt,
        ])->save();

        GroupInvitation::query()
            ->where('source_group_member_id', $member->id)
            ->where('status', GroupInvitation::STATUS_PENDING)
            ->update([
                'status' => GroupInvitation::STATUS_DECLINED,
                'responded_at' => $declinedAt,
                'accepted_at' => null,
                'updated_at' => $declinedAt,
            ]);

        return $member->fresh(['group.owner.profile', 'user.profile', 'invitedBy.profile']);
    }

    public function updateMemberRole(GroupMember $member, User $actor, string $role): GroupMember
    {
        $this->guardManageMembers($member->group, $actor);
        $role = Group::normalizeMemberRole($role);

        if ($member->role === Group::ROLE_OWNER) {
            throw ValidationException::withMessages([
                'member' => 'The group owner role cannot be changed.',
            ]);
        }

        if (! in_array($role, [Group::ROLE_ADMIN, Group::ROLE_EDITOR, Group::ROLE_MEMBER], true)) {
            throw ValidationException::withMessages([
                'role' => 'Choose a valid group role.',
            ]);
        }

        $member->forceFill(['role' => $role])->save();
        $this->notifications->notifyGroupRoleChanged($member->user, $actor, $member->group, Group::displayRole($role) ?? $role);

        return $member->fresh(['group.owner.profile', 'user.profile', 'invitedBy.profile']);
    }

    public function updatePermissionOverrides(GroupMember $member, User $actor, array $overrides): GroupMember
    {
        if (! $member->group->canManageMemberPermissions($actor) && ! $actor->isAdmin()) {
            throw ValidationException::withMessages([
                'member' => 'You are not allowed to manage group member permissions.',
            ]);
        }

        if ($member->role === Group::ROLE_OWNER) {
            throw ValidationException::withMessages([
                'member' => 'The group owner already has full permissions.',
            ]);
        }

        $normalized = collect($overrides)
            ->map(function ($override): ?array {
                if (is_string($override)) {
                    $key = trim($override);

                    return $key !== '' && in_array($key, Group::allowedPermissionOverrides(), true)
                        ? ['key' => $key, 'is_allowed' => true]
                        : null;
                }

                if (! is_array($override)) {
                    return null;
                }

                $key = trim((string) ($override['key'] ?? ''));
                if ($key === '' || ! in_array($key, Group::allowedPermissionOverrides(), true)) {
                    return null;
                }

                return [
                    'key' => $key,
                    'is_allowed' => (bool) ($override['is_allowed'] ?? false),
                ];
            })
            ->filter()
            ->unique(fn (array $override): string => $override['key'])
            ->values()
            ->all();

        $member->forceFill([
            'permission_overrides_json' => $normalized,
        ])->save();

        return $member->fresh(['group.owner.profile', 'user.profile', 'invitedBy.profile']);
    }

    public function revokeMember(GroupMember $member, User $actor): void
    {
        $this->guardManageMembers($member->group, $actor);

        $wasActiveMember = $member->status === Group::STATUS_ACTIVE;

        if ($member->role === Group::ROLE_OWNER) {
            throw ValidationException::withMessages([
                'member' => 'The group owner cannot be removed.',
            ]);
        }

        $member->forceFill([
            'status' => Group::STATUS_REVOKED,
            'expires_at' => null,
            'revoked_at' => now(),
        ])->save();

        if ($wasActiveMember) {
            $this->notifications->notifyGroupMemberRemoved($member->user, $actor, $member->group);
        }
    }

    public function revokeInvitation(GroupInvitation $invitation, User $actor): GroupInvitation
    {
        $this->guardManageMembers($invitation->group, $actor);

        if ($invitation->status !== GroupInvitation::STATUS_PENDING) {
            throw ValidationException::withMessages([
                'invitation' => 'Only pending invitations can be revoked.',
            ]);
        }

        $revokedAt = now();

        $invitation->forceFill([
            'status' => GroupInvitation::STATUS_REVOKED,
            'responded_at' => $revokedAt,
            'revoked_at' => $revokedAt,
        ])->save();

        $this->syncLegacyMemberFromInvitation($invitation, Group::STATUS_REVOKED, $revokedAt);

        return $invitation->fresh(['group.owner.profile', 'invitedUser.profile', 'invitedBy.profile']);
    }

    public function transferOwnership(Group $group, GroupMember $member, User $actor): Group
    {
        if (! $group->isOwnedBy($actor) && ! $actor->isAdmin()) {
            throw ValidationException::withMessages([
                'member' => 'Only the group owner can transfer ownership.',
            ]);
        }

        if ((int) $member->group_id !== (int) $group->id) {
            throw ValidationException::withMessages([
                'member' => 'This member does not belong to the selected group.',
            ]);
        }

        if ($member->status !== Group::STATUS_ACTIVE) {
            throw ValidationException::withMessages([
                'member' => 'Only active members can become the new owner.',
            ]);
        }

        return DB::transaction(function () use ($group, $member): Group {
            GroupMember::query()
                ->where('group_id', $group->id)
                ->where('user_id', $group->owner_user_id)
                ->update([
                    'role' => Group::ROLE_ADMIN,
                    'updated_at' => now(),
                ]);

            $member->forceFill([
                'role' => Group::ROLE_OWNER,
                'status' => Group::STATUS_ACTIVE,
                'expires_at' => null,
                'accepted_at' => $member->accepted_at ?? now(),
            ])->save();

            $group->forceFill([
                'owner_user_id' => $member->user_id,
                'last_activity_at' => now(),
            ])->save();

            $this->ensureOwnerMembership($group->fresh());

            return $group->fresh(['owner.profile']);
        });
    }

    public function expirePendingInvites(): int
    {
        $expired = GroupInvitation::query()
            ->with('sourceGroupMember')
            ->where('status', GroupInvitation::STATUS_PENDING)
            ->whereNotNull('expires_at')
            ->where('expires_at', '<=', now())
            ->get();

        foreach ($expired as $invitation) {
            $expiredAt = now();
            $invitation->forceFill([
                'status' => GroupInvitation::STATUS_EXPIRED,
                'responded_at' => $expiredAt,
                'revoked_at' => $expiredAt,
            ])->save();

            $this->syncLegacyMemberFromInvitation($invitation, Group::STATUS_REVOKED, $expiredAt);
        }

        return $expired->count();
    }

    public function activeContributorIds(Group $group): array
    {
        $activeIds = $group->members()
            ->where('status', Group::STATUS_ACTIVE)
            ->whereIn('role', [Group::ROLE_OWNER, Group::ROLE_ADMIN, Group::ROLE_EDITOR, Group::ROLE_MEMBER])
            ->pluck('user_id')
            ->map(static fn ($id): int => (int) $id)
            ->all();

        if (! in_array((int) $group->owner_user_id, $activeIds, true)) {
            $activeIds[] = (int) $group->owner_user_id;
        }

        return array_values(array_unique($activeIds));
    }

    public function mapMembers(Group $group, ?User $viewer = null): array
    {
        $this->expirePendingInvites();

        $members = $group->members()
            ->with(['user.profile', 'invitedBy.profile'])
            ->where('status', Group::STATUS_ACTIVE)
            ->orderByRaw("CASE role WHEN 'owner' THEN 0 WHEN 'admin' THEN 1 WHEN 'editor' THEN 2 ELSE 3 END")
            ->orderBy('created_at')
            ->get();

        return $members->map(fn (GroupMember $member): array => $this->mapMemberRow($member, $group, $viewer))->all();
    }

    public function mapInvitations(Group $group, ?User $viewer = null): array
    {
        $this->expirePendingInvites();

        return $group->invitations()
            ->with(['invitedUser.profile', 'invitedBy.profile'])
            ->whereIn('status', [
                GroupInvitation::STATUS_PENDING,
                GroupInvitation::STATUS_REVOKED,
                GroupInvitation::STATUS_DECLINED,
                GroupInvitation::STATUS_EXPIRED,
            ])
            ->orderByDesc('invited_at')
            ->orderByDesc('updated_at')
            ->get()
            ->map(fn (GroupInvitation $invitation): array => $this->mapInvitationRow($invitation, $group, $viewer))
            ->values()
            ->all();
    }

    public function pendingInviteCount(Group $group): int
    {
        $this->expirePendingInvites();

        return (int) $group->invitations()
            ->where('status', GroupInvitation::STATUS_PENDING)
            ->count();
    }

    public function pendingInvitationsForUser(User $user): array
    {
        $this->expirePendingInvites();

        return $user->groupInvitations()
            ->with(['group.owner.profile', 'group.members', 'invitedBy.profile'])
            ->where('status', GroupInvitation::STATUS_PENDING)
            ->orderByDesc('invited_at')
            ->get()
            ->map(fn (GroupInvitation $invitation): array => [
                'id' => (int) $invitation->id,
                'group' => $invitation->group ? [
                    'id' => (int) $invitation->group->id,
                    'name' => (string) $invitation->group->name,
                    'slug' => (string) $invitation->group->slug,
                    'avatar_url' => $invitation->group->avatarUrl(),
                    'counts' => [
                        'artworks' => (int) $invitation->group->artworks_count,
                        'collections' => (int) $invitation->group->collections_count,
                        'followers' => (int) $invitation->group->followers_count,
                    ],
                ] : null,
                'role' => Group::displayRole((string) $invitation->role) ?? (string) $invitation->role,
                'invited_at' => $invitation->invited_at?->toISOString(),
                'expires_at' => $invitation->expires_at?->toISOString(),
                'accept_url' => route('studio.groups.invitations.accept', ['invitation' => $invitation]),
                'decline_url' => route('studio.groups.invitations.decline', ['invitation' => $invitation]),
                'invited_by' => $invitation->invitedBy ? [
                    'name' => $invitation->invitedBy->name,
                    'username' => $invitation->invitedBy->username,
                ] : null,
            ])
            ->values()
            ->all();
    }

    public function contributorOptions(Group $group): array
    {
        return User::query()
            ->with('profile:user_id,avatar_hash')
            ->whereIn('id', $this->activeContributorIds($group))
            ->orderBy('username')
            ->get()
            ->map(fn (User $user): array => [
                'id' => (int) $user->id,
                'name' => $user->name,
                'username' => $user->username,
                'avatar_url' => AvatarUrl::forUser((int) $user->id, $user->profile?->avatar_hash, 64),
            ])
            ->values()
            ->all();
    }

    private function guardManageMembers(Group $group, User $actor): void
    {
        if (! $group->canManageMembers($actor) && ! $actor->isAdmin()) {
            throw ValidationException::withMessages([
                'group' => 'You are not allowed to manage this group.',
            ]);
        }
    }

    private function expireMemberIfNeeded(GroupMember $member): void
    {
        if ($member->status !== Group::STATUS_PENDING || ! $member->expires_at || $member->expires_at->isFuture()) {
            return;
        }

        $member->forceFill([
            'status' => Group::STATUS_REVOKED,
            'revoked_at' => Carbon::now(),
        ])->save();

        GroupInvitation::query()
            ->where('source_group_member_id', $member->id)
            ->where('status', GroupInvitation::STATUS_PENDING)
            ->update([
                'status' => GroupInvitation::STATUS_EXPIRED,
                'responded_at' => now(),
                'revoked_at' => now(),
                'updated_at' => now(),
            ]);
    }

    private function expireInvitationIfNeeded(GroupInvitation $invitation): void
    {
        if ($invitation->status !== GroupInvitation::STATUS_PENDING || ! $invitation->expires_at || $invitation->expires_at->isFuture()) {
            return;
        }

        $expiredAt = Carbon::now();

        $invitation->forceFill([
            'status' => GroupInvitation::STATUS_EXPIRED,
            'responded_at' => $expiredAt,
            'revoked_at' => $expiredAt,
        ])->save();

        $this->syncLegacyMemberFromInvitation($invitation, Group::STATUS_REVOKED, $expiredAt);
    }

    private function mapMemberRow(GroupMember $member, Group $group, ?User $viewer = null): array
    {
        $user = $member->user;

        return [
            'id' => (int) $member->id,
            'user_id' => (int) $member->user_id,
            'role' => (string) $member->role,
            'role_label' => Group::displayRole((string) $member->role),
            'status' => (string) $member->status,
            'permission_overrides' => collect($member->permission_overrides_json ?? [])
                ->map(function ($override): ?array {
                    if (is_array($override)) {
                        $key = trim((string) ($override['key'] ?? ''));

                        return $key !== '' ? ['key' => $key, 'is_allowed' => (bool) ($override['is_allowed'] ?? false)] : null;
                    }

                    $key = trim((string) $override);

                    return $key !== '' ? ['key' => $key, 'is_allowed' => true] : null;
                })
                ->filter()
                ->values()
                ->all(),
            'note' => $member->note,
            'invited_at' => $member->invited_at?->toISOString(),
            'expires_at' => $member->expires_at?->toISOString(),
            'accepted_at' => $member->accepted_at?->toISOString(),
            'is_expired' => $member->status === Group::STATUS_REVOKED && $member->expires_at !== null && $member->expires_at->lte(now()) && $member->accepted_at === null,
            'can_accept' => $viewer !== null && (int) $member->user_id === (int) $viewer->id && $member->status === Group::STATUS_PENDING,
            'can_decline' => $viewer !== null && (int) $member->user_id === (int) $viewer->id && $member->status === Group::STATUS_PENDING,
            'can_revoke' => $viewer !== null && $group->canManageMembers($viewer) && $member->role !== Group::ROLE_OWNER,
            'can_transfer' => $viewer !== null
                && $group->isOwnedBy($viewer)
                && $member->status === Group::STATUS_ACTIVE
                && $member->role !== Group::ROLE_OWNER,
            'can_manage_permissions' => $viewer !== null
                && $group->canManageMemberPermissions($viewer)
                && $member->status === Group::STATUS_ACTIVE
                && $member->role !== Group::ROLE_OWNER,
            'user' => [
                'id' => (int) $user->id,
                'name' => $user->name,
                'username' => $user->username,
                'avatar_url' => AvatarUrl::forUser((int) $user->id, $user->profile?->avatar_hash, 72),
                'profile_url' => route('profile.show', ['username' => strtolower((string) $user->username)]),
            ],
            'invited_by' => $member->invitedBy ? [
                'id' => (int) $member->invitedBy->id,
                'username' => $member->invitedBy->username,
                'name' => $member->invitedBy->name,
            ] : null,
        ];
    }

    private function mapInvitationRow(GroupInvitation $invitation, Group $group, ?User $viewer = null): array
    {
        $user = $invitation->invitedUser;
        $displayStatus = $invitation->status === GroupInvitation::STATUS_PENDING ? GroupInvitation::STATUS_PENDING : Group::STATUS_REVOKED;

        return [
            'id' => (int) $invitation->id,
            'user_id' => (int) $invitation->invited_user_id,
            'role' => (string) $invitation->role,
            'role_label' => Group::displayRole((string) $invitation->role),
            'status' => $displayStatus,
            'status_raw' => (string) $invitation->status,
            'note' => $invitation->note,
            'invited_at' => $invitation->invited_at?->toISOString(),
            'expires_at' => $invitation->expires_at?->toISOString(),
            'accepted_at' => $invitation->accepted_at?->toISOString(),
            'is_expired' => $invitation->status === GroupInvitation::STATUS_EXPIRED,
            'can_accept' => $viewer !== null && (int) $invitation->invited_user_id === (int) $viewer->id && $invitation->status === GroupInvitation::STATUS_PENDING,
            'can_decline' => $viewer !== null && (int) $invitation->invited_user_id === (int) $viewer->id && $invitation->status === GroupInvitation::STATUS_PENDING,
            'can_revoke' => $viewer !== null && $group->canManageMembers($viewer) && $invitation->status === GroupInvitation::STATUS_PENDING,
            'can_transfer' => false,
            'accept_url' => $invitation->status === GroupInvitation::STATUS_PENDING ? route('studio.groups.invitations.accept', ['invitation' => $invitation]) : null,
            'decline_url' => $invitation->status === GroupInvitation::STATUS_PENDING ? route('studio.groups.invitations.decline', ['invitation' => $invitation]) : null,
            'revoke_url' => $viewer !== null && $group->canManageMembers($viewer) ? route('studio.groups.invitations.destroy', ['group' => $group, 'invitation' => $invitation]) : null,
            'user' => $user ? [
                'id' => (int) $user->id,
                'name' => $user->name,
                'username' => $user->username,
                'avatar_url' => AvatarUrl::forUser((int) $user->id, $user->profile?->avatar_hash, 72),
                'profile_url' => route('profile.show', ['username' => strtolower((string) $user->username)]),
            ] : null,
            'invited_by' => $invitation->invitedBy ? [
                'id' => (int) $invitation->invitedBy->id,
                'username' => $invitation->invitedBy->username,
                'name' => $invitation->invitedBy->name,
            ] : null,
        ];
    }

    private function syncLegacyMemberFromInvitation(GroupInvitation $invitation, string $memberStatus, Carbon $timestamp): void
    {
        if (! $invitation->source_group_member_id) {
            return;
        }

        $member = $invitation->sourceGroupMember()->first();
        if (! $member) {
            return;
        }

        $member->forceFill([
            'status' => $memberStatus,
            'expires_at' => $memberStatus === Group::STATUS_ACTIVE ? null : $member->expires_at,
            'accepted_at' => $memberStatus === Group::STATUS_ACTIVE ? $timestamp : null,
            'revoked_at' => $memberStatus === Group::STATUS_ACTIVE ? null : $timestamp,
        ])->save();
    }
}