<?php

namespace XF\Entity;

use XF\Api\Result\EntityResult;
use XF\BbCode\RenderableContentInterface;
use XF\Mvc\Entity\ArrayCollection;
use XF\Mvc\Entity\Entity;
use XF\Mvc\Entity\Structure;
use XF\Repository\AttachmentRepository;
use XF\Repository\ProfilePostRepository;
use XF\Repository\UserAlertRepository;
use XF\Spam\ContentChecker;

/**
 * COLUMNS
 * @property int|null $profile_post_id
 * @property int $profile_user_id
 * @property int $user_id
 * @property string $username
 * @property int $post_date
 * @property string $message
 * @property int $ip_id
 * @property string $message_state
 * @property int $attach_count
 * @property int $warning_id
 * @property string $warning_message
 * @property int $comment_count
 * @property int $first_comment_date
 * @property int $last_comment_date
 * @property array $latest_comment_ids
 * @property array|null $embed_metadata
 * @property int $reaction_score
 * @property array $reactions_
 * @property array $reaction_users_
 *
 * GETTERS
 * @property-read array $comment_ids
 * @property-read ArrayCollection|null $LatestComments
 * @property-read mixed $Unfurls
 * @property mixed $reactions
 * @property mixed $reaction_users
 * @property-read array $Embeds
 *
 * RELATIONS
 * @property-read User|null $ProfileUser
 * @property-read User|null $User
 * @property-read DeletionLog|null $DeletionLog
 * @property-read ApprovalQueue|null $ApprovalQueue
 * @property-read \XF\Mvc\Entity\AbstractCollection<\XF\Entity\ProfilePostComment> $Comments
 * @property-read \XF\Mvc\Entity\AbstractCollection<\XF\Entity\Attachment> $Attachments
 * @property-read \XF\Mvc\Entity\AbstractCollection<\XF\Entity\ReactionContent> $Reactions
 */
class ProfilePost extends Entity implements RenderableContentInterface, LinkableInterface, ViewableInterface, ContainableInterface, DatableInterface
{
	use ContainableTrait;
	use DatableTrait;
	use EmbedRendererTrait;
	use EmbedResolverTrait;
	use ReactionTrait;

	public function canView(&$error = null)
	{
		$visitor = \XF::visitor();

		if (!$this->ProfileUser)
		{
			return false;
		}

		if (!$this->ProfileUser->canViewPostsOnProfile($error))
		{
			return false;
		}

		if ($this->message_state == 'moderated')
		{
			if (
				!$visitor->hasPermission('profilePost', 'viewModerated')
				&& (!$visitor->user_id || $visitor->user_id != $this->user_id)
			)
			{
				$error = \XF::phraseDeferred('requested_profile_post_not_found');
				return false;
			}
		}
		else if ($this->message_state == 'deleted')
		{
			if (!$visitor->hasPermission('profilePost', 'viewDeleted'))
			{
				$error = \XF::phraseDeferred('requested_profile_post_not_found');
				return false;
			}
		}

		return true;
	}

	public function canViewAttachments(&$error = null): bool
	{
		return \XF::visitor()->hasPermission('profilePost', 'viewAttachment');
	}

	public function canUploadAndManageAttachments(): bool
	{
		$profileUser = $this->ProfileUser;
		if (!$profileUser)
		{
			return false;
		}

		return $profileUser->canUploadAndManageAttachmentsOnProfile();
	}

	public function canUploadVideos(): bool
	{
		$profileUser = $this->ProfileUser;
		if (!$profileUser)
		{
			return false;
		}

		return $profileUser->canUploadVideosOnProfile();
	}

	public function canUseInlineModeration(&$error = null)
	{
		$visitor = \XF::visitor();
		return ($visitor->user_id && $visitor->hasPermission('profilePost', 'inlineMod'));
	}

	public function canEdit(&$error = null)
	{
		$visitor = \XF::visitor();

		if (!$visitor->user_id)
		{
			return false;
		}

		if ($visitor->user_id == $this->user_id)
		{
			return $visitor->hasPermission('profilePost', 'editOwn');
		}
		else
		{
			return $visitor->hasPermission('profilePost', 'editAny');
		}
	}

	public function canDelete($type = 'soft', &$error = null)
	{
		$visitor = \XF::visitor();
		if (!$visitor->user_id)
		{
			return false;
		}

		if ($type != 'soft' && !$visitor->hasPermission('profilePost', 'hardDeleteAny'))
		{
			return false;
		}

		if ($visitor->hasPermission('profilePost', 'deleteAny'))
		{
			return true;
		}

		return (
			(
				$visitor->user_id == $this->profile_user_id
				&& $visitor->hasPermission('profilePost', 'manageOwn')
			)
			||
			(
				$visitor->user_id == $this->user_id
				&& $visitor->hasPermission('profilePost', 'deleteOwn')
			)
		);
	}

	public function canUndelete(&$error = null)
	{
		$visitor = \XF::visitor();
		return ($visitor->user_id && $visitor->hasPermission('profilePost', 'undelete'));
	}

	public function canApproveUnapprove(&$error = null)
	{
		$visitor = \XF::visitor();
		return ($visitor->user_id && $visitor->hasPermission('profilePost', 'approveUnapprove'));
	}

	public function canWarn(&$error = null)
	{
		$visitor = \XF::visitor();

		if (!$this->user_id
			|| !$visitor->user_id
			|| $this->user_id == $visitor->user_id
			|| !$visitor->hasPermission('profilePost', 'warn')
		)
		{
			return false;
		}

		if ($this->warning_id)
		{
			$error = \XF::phraseDeferred('user_has_already_been_warned_for_this_content');
			return false;
		}

		return ($this->User && $this->User->isWarnable());
	}

	public function canReport(&$error = null, ?User $asUser = null)
	{
		$asUser = $asUser ?: \XF::visitor();
		return $asUser->canReport($error);
	}

	public function canReact(&$error = null)
	{
		$visitor = \XF::visitor();
		if (!$visitor->user_id)
		{
			return false;
		}

		if ($this->message_state != 'visible')
		{
			return false;
		}

		if ($this->user_id == $visitor->user_id)
		{
			$error = \XF::phraseDeferred('reacting_to_your_own_content_is_considered_cheating');
			return false;
		}

		return $visitor->hasPermission('profilePost', 'react');
	}

	public function canComment(&$error = null)
	{
		$visitor = \XF::visitor();

		return (
			$this->message_state == 'visible'
				&& $visitor->user_id
				&& $visitor->hasPermission('profilePost', 'view')
				&& $visitor->hasPermission('profilePost', 'comment')
				&& $this->ProfileUser->isPrivacyCheckMet('allow_post_profile', $visitor)
		);
	}

	public function canAttach(&$error = null)
	{
		$visitor = \XF::visitor();

		return (
			$this->message_state == 'visible'
			&& $visitor->user_id
			&& $visitor->hasPermission('profilePost', 'uploadAttachment')
		);
	}

	public function canViewDeletedComments()
	{
		return \XF::visitor()->hasPermission('profilePost', 'viewDeleted');
	}

	public function canViewModeratedComments()
	{
		return \XF::visitor()->hasPermission('profilePost', 'viewModerated');
	}

	public function canSendModeratorActionAlert()
	{
		$visitor = \XF::visitor();

		if (!$visitor->user_id || $visitor->user_id == $this->user_id)
		{
			return false;
		}

		if ($this->message_state != 'visible')
		{
			return false;
		}

		return (
			$visitor->hasPermission('profilePost', 'deleteAny')
			|| $visitor->hasPermission('profilePost', 'editAny')
		);
	}

	public function hasMoreComments()
	{
		if ($this->comment_count > 3)
		{
			return true;
		}

		$visitor = \XF::visitor();

		$canViewDeleted = $visitor->hasPermission('profilePost', 'viewDeleted');
		$canViewModerated = $visitor->hasPermission('profilePost', 'viewModerated');

		if (!$canViewDeleted && !$canViewModerated)
		{
			return false;
		}

		$viewableCommentCount = 0;

		foreach ($this->latest_comment_ids AS $commentId => $state)
		{
			switch ($state[0])
			{
				case 'visible':
					$viewableCommentCount++;
					break;

				case 'moderated':
					if ($canViewModerated)
					{
						$viewableCommentCount++;
					}
					break;

				case 'deleted':
					if ($canViewDeleted)
					{
						$viewableCommentCount++;
					}
					break;
			}

			if ($viewableCommentCount > 3)
			{
				return true;
			}
		}

		return false;
	}

	public function isVisible()
	{
		return ($this->message_state == 'visible');
	}

	public function isAttachmentEmbedded($attachmentId)
	{
		if (!$this->embed_metadata)
		{
			return false;
		}

		if ($attachmentId instanceof Attachment)
		{
			$attachmentId = $attachmentId->attachment_id;
		}

		return isset($this->embed_metadata['attachments'][$attachmentId]);
	}

	public function isIgnored()
	{
		return \XF::visitor()->isIgnoring($this->user_id);
	}

	public function canCleanSpam()
	{
		return (\XF::visitor()->canCleanSpam() && $this->User && $this->User->isPossibleSpammer());
	}

	/**
	 * @return array
	 */
	public function getCommentIds()
	{
		return $this->db()->fetchAllColumn("
			SELECT profile_post_comment_id
			FROM xf_profile_post_comment
			WHERE profile_post_id = ?
			ORDER BY comment_date
		", $this->profile_post_id);
	}

	/**
	 * @return ArrayCollection|null
	 */
	public function getLatestComments()
	{
		$this->repository(ProfilePostRepository::class)->addCommentsToProfilePosts([$this->profile_post_id => $this]);

		if (isset($this->_getterCache['LatestComments']))
		{
			return $this->_getterCache['LatestComments'];
		}
		else
		{
			return $this->_em->getBasicCollection([]);
		}
	}

	public function setLatestComments(array $latest)
	{
		$this->_getterCache['LatestComments'] = $this->_em->getBasicCollection($latest);
	}

	public function commentAdded(ProfilePostComment $comment)
	{
		$this->comment_count++;

		if (!$this->first_comment_date || $comment->comment_date < $this->first_comment_date)
		{
			$this->first_comment_date = $comment->comment_date;
		}

		if ($comment->comment_date > $this->last_comment_date)
		{
			$this->last_comment_date = $comment->comment_date;
		}

		$this->rebuildCommentIds();
	}

	public function commentRemoved(ProfilePostComment $comment)
	{
		$this->comment_count--;

		if ($this->first_comment_date == $comment->comment_date)
		{
			if (!$this->comment_count)
			{
				$this->first_comment_date = 0;
			}
			else
			{
				$this->rebuildFirstCommentInfo();
			}
		}

		if ($this->last_comment_date == $comment->comment_date)
		{
			if (!$this->comment_count)
			{
				$this->last_comment_date = 0;
			}
			else
			{
				$this->rebuildLastCommentInfo();
			}
		}

		$this->rebuildCommentIds();
	}

	public function rebuildCounters()
	{
		if (!$this->rebuildFirstCommentInfo())
		{
			// no visible comments, we know we've set the last comment and count to 0
		}
		else
		{
			$this->rebuildLastCommentInfo();
			$this->rebuildCommentCount();
		}

		// since this contains non-visible comments, we always have to rebuild
		$this->rebuildLatestCommentIds();

		return true;
	}

	public function rebuildFirstCommentInfo()
	{
		$firstComment = $this->db()->fetchRow("
			SELECT profile_post_comment_id, comment_date, user_id, username
			FROM xf_profile_post_comment
			WHERE profile_post_id = ?
				AND message_state = 'visible'
			ORDER BY comment_date
			LIMIT 1
		", $this->profile_post_id);

		if (!$firstComment)
		{
			$this->comment_count = 0;
			$this->first_comment_date = 0;
			$this->last_comment_date = 0;
			return false;
		}
		else
		{
			$this->first_comment_date = $firstComment['comment_date'];
			return true;
		}
	}

	public function rebuildLastCommentInfo()
	{
		$lastComment = $this->db()->fetchRow("
			SELECT profile_post_comment_id, comment_date, user_id, username
			FROM xf_profile_post_comment
			WHERE profile_post_id = ?
				AND message_state = 'visible'
			ORDER BY comment_date DESC
			LIMIT 1
		", $this->profile_post_id);

		if (!$lastComment)
		{
			$this->comment_count = 0;
			$this->first_comment_date = 0;
			$this->last_comment_date = 0;
			return false;
		}
		else
		{
			$this->last_comment_date = $lastComment['comment_date'];
			return true;
		}
	}

	public function rebuildCommentCount()
	{
		$visibleComments = $this->db()->fetchOne("
			SELECT COUNT(*)
			FROM xf_profile_post_comment
			WHERE profile_post_id = ?
				AND message_state = 'visible'
		", $this->profile_post_id);

		$this->comment_count = $visibleComments;

		return $this->comment_count;
	}

	public function rebuildCommentIds()
	{
		$this->rebuildLatestCommentIds();
		unset($this->_getterCache['comment_ids']);
	}

	public function rebuildLatestCommentIds()
	{
		$this->latest_comment_ids = $this->repository(ProfilePostRepository::class)->getLatestCommentCache($this);
	}

	public function getBbCodeRenderOptions($context, $type)
	{
		$renderOptions = [
			'entity' => $this,
			'user' => $this->User,
			'treatAsStructuredText' => true,
			'attachments' => $this->attach_count ? $this->Attachments : [],
			'viewAttachments' => $this->canViewAttachments(),
			'unfurls' => $this->Unfurls ?: [],
			'images' => $this->embed_metadata['images'] ?? [],
		];

		$this->addEmbedRendererBbCodeOptions($renderOptions, $context, $type);

		return $renderOptions;
	}

	public function getUnfurls()
	{
		return $this->_getterCache['Unfurls'] ?? [];
	}

	public function setUnfurls($unfurls)
	{
		$this->_getterCache['Unfurls'] = $unfurls;
	}

	protected function _postSave()
	{
		$visibilityChange = $this->isStateChanged('message_state', 'visible');
		$approvalChange = $this->isStateChanged('message_state', 'moderated');
		$deletionChange = $this->isStateChanged('message_state', 'deleted');

		if ($this->isUpdate())
		{
			if ($visibilityChange == 'enter')
			{
				$this->profilePostMadeVisible();

				if ($approvalChange)
				{
					$this->submitHamData();
				}
			}
			else if ($visibilityChange == 'leave')
			{
				$this->profilePostHidden();
			}

			if ($deletionChange == 'leave' && $this->DeletionLog)
			{
				$this->DeletionLog->delete();
			}

			if ($approvalChange == 'leave' && $this->ApprovalQueue)
			{
				$this->ApprovalQueue->delete();
			}
		}

		if ($approvalChange == 'enter')
		{
			$approvalQueue = $this->getRelationOrDefault('ApprovalQueue', false);
			$approvalQueue->content_date = $this->post_date;
			$approvalQueue->save();
		}
		else if ($deletionChange == 'enter' && !$this->DeletionLog)
		{
			$delLog = $this->getRelationOrDefault('DeletionLog', false);
			$delLog->setFromVisitor();
			$delLog->save();
		}

		if ($this->isUpdate() && $this->getOption('log_moderator'))
		{
			$this->app()->logger()->logModeratorChanges('profile_post', $this);
		}
	}

	protected function profilePostMadeVisible()
	{
	}

	protected function profilePostHidden($hardDelete = false)
	{
		/** @var UserAlertRepository $alertRepo */
		$alertRepo = $this->repository(UserAlertRepository::class);
		$alertRepo->fastDeleteAlertsForContent('profile_post', $this->profile_post_id);
		$alertRepo->fastDeleteAlertsForContent('profile_post_comment', $this->comment_ids);
	}

	protected function submitHamData()
	{
		/** @var ContentChecker $submitter */
		$submitter = $this->app()->container('spam.contentHamSubmitter');
		$submitter->submitHam('profile_post', $this->profile_post_id);
	}

	protected function _postDelete()
	{
		if ($this->message_state == 'visible')
		{
			$this->profilePostHidden(true);
		}

		if ($this->message_state == 'deleted' && $this->DeletionLog)
		{
			$this->DeletionLog->delete();
		}

		if ($this->message_state == 'moderated' && $this->ApprovalQueue)
		{
			$this->ApprovalQueue->delete();
		}

		if ($this->getOption('log_moderator'))
		{
			$this->app()->logger()->logModeratorAction('profile_post', $this, 'delete_hard');
		}

		/** @var AttachmentRepository $attachRepo */
		$attachRepo = $this->repository(AttachmentRepository::class);
		$attachRepo->fastDeleteContentAttachments('profile_post', $this->profile_post_id);

		$db = $this->db();
		$commentIds = $this->comment_ids;
		if ($commentIds)
		{
			$quotedIds = $db->quote($commentIds);

			$db->delete('xf_profile_post_comment', "profile_post_comment_id IN ({$quotedIds})");
			$db->delete('xf_approval_queue', "content_id IN ({$quotedIds}) AND content_type = 'profile_post_comment'");
			$db->delete('xf_deletion_log', "content_id IN ({$quotedIds}) AND content_type = 'profile_post_comment'");
		}
	}

	public function softDelete($reason = '', ?User $byUser = null)
	{
		$byUser = $byUser ?: \XF::visitor();

		if ($this->message_state == 'deleted')
		{
			return false;
		}

		$this->message_state = 'deleted';

		/** @var DeletionLog $deletionLog */
		$deletionLog = $this->getRelationOrDefault('DeletionLog');
		$deletionLog->setFromUser($byUser);
		$deletionLog->delete_reason = $reason;

		$this->save();

		return true;
	}

	public function getNewComment()
	{
		$comment = $this->_em->create(ProfilePostComment::class);
		$comment->profile_post_id = $this->profile_post_id;

		return $comment;
	}

	public function getNewContentState()
	{
		$visitor = \XF::visitor();

		if ($visitor->user_id && $visitor->hasPermission('profilePost', 'approveUnapprove'))
		{
			return 'visible';
		}

		if (!$visitor->hasPermission('general', 'submitWithoutApproval'))
		{
			return 'moderated';
		}

		return 'visible';
	}

	/**
	 * @param EntityResult $result
	 * @param int $verbosity
	 * @param array $options
	 *
	 * @api-out str $username
	 * @api-out str $message_parsed HTML parsed version of the message contents.
	 * @api-out bool $can_edit
	 * @api-out bool $can_soft_delete
	 * @api-out bool $can_hard_delete
	 * @api-out bool $can_react
	 * @api-out bool $can_view_attachments
	 * @api-out string $view_url
	 * @api-out User $ProfileUser <cond> If requested by context, the user this profile post was left for.
	 * @api-out Attachment[] $Attachments <cond> Attachments to this profile post, if it has any.
	 * @api-out ProfilePostComment[] $LatestComments <cond> If requested, the most recent comments on this profile post.
	 * @api-see XF\Entity\ReactionTrait::addReactionStateToApiResult
	 */
	protected function setupApiResultData(
		EntityResult $result,
		$verbosity = self::VERBOSITY_NORMAL,
		array $options = []
	)
	{
		$result->username = $this->User ? $this->User->username : $this->username;

		if (!empty($options['with_profile']))
		{
			$result->includeRelation('ProfileUser');
		}

		if (!empty($options['with_latest']))
		{
			$result->includeGetter('LatestComments');
		}

		if ($this->attach_count)
		{
			// note that we allow viewing of thumbs and metadata, regardless of permissions, when viewing the
			// content an attachment is connected to
			$result->includeRelation('Attachments');
		}

		$result->message_parsed = $this->app()->bbCode()->render($this->message, 'apiHtml', 'profile_post:api', $this);

		$this->addReactionStateToApiResult($result);

		$result->view_url = $this->getContentUrl(true);

		if ($result->getResultType() === EntityResult::TYPE_API)
		{
			$result->can_edit = $this->canEdit();
			$result->can_soft_delete = $this->canDelete();
			$result->can_hard_delete = $this->canDelete('hard');
			$result->can_react = $this->canReact();
			$result->can_view_attachments = $this->canViewAttachments();
		}
	}

	public function getContentContainerIdColumn(): string
	{
		return 'user_id';
	}

	public function getContentContainerType(): string
	{
		return 'user';
	}

	public function getContentDateColumn(): string
	{
		return 'post_date';
	}

	public function getContentUrl(bool $canonical = false, array $extraParams = [], $hash = null)
	{
		$route = $canonical ? 'canonical:profile-posts' : 'profile-posts';
		return $this->app()->router('public')->buildLink($route, $this, $extraParams, $hash);
	}

	public function getContentPublicRoute()
	{
		return 'profile-posts';
	}

	public function getContentTitle(string $context = '')
	{
		return \XF::phrase('profile_post_by_x', ['name' => $this->username]);
	}

	public static function getStructure(Structure $structure)
	{
		$structure->table = 'xf_profile_post';
		$structure->shortName = 'XF:ProfilePost';
		$structure->contentType = 'profile_post';
		$structure->primaryKey = 'profile_post_id';
		$structure->columns = [
			'profile_post_id' => ['type' => self::UINT, 'autoIncrement' => true, 'nullable' => true],
			'profile_user_id' => ['type' => self::UINT, 'required' => true, 'api' => true],
			'user_id' => ['type' => self::UINT, 'required' => true, 'api' => true],
			'username' => ['type' => self::STR, 'maxLength' => 50,
				'required' => 'please_enter_valid_name',
			],
			'post_date' => ['type' => self::UINT, 'required' => true, 'default' => \XF::$time, 'api' => true],
			'message' => ['type' => self::STR,
				'required' => 'please_enter_valid_message', 'api' => true,
			],
			'ip_id' => ['type' => self::UINT, 'default' => 0],
			'message_state' => ['type' => self::STR, 'default' => 'visible',
				'allowedValues' => ['visible', 'moderated', 'deleted'], 'api' => true,
			],
			'attach_count' => ['type' => self::UINT, 'max' => 65535, 'forced' => true, 'default' => 0],
			'warning_id' => ['type' => self::UINT, 'default' => 0],
			'warning_message' => ['type' => self::STR, 'default' => '', 'maxLength' => 255, 'api' => true],
			'comment_count' => ['type' => self::UINT, 'forced' => true, 'default' => 0, 'api' => true],
			'first_comment_date' => ['type' => self::UINT, 'default' => 0, 'api' => true],
			'last_comment_date' => ['type' => self::UINT, 'default' => 0, 'api' => true],
			'latest_comment_ids' => ['type' => self::JSON_ARRAY, 'default' => []],
			'embed_metadata' => ['type' => self::JSON_ARRAY, 'nullable' => true, 'default' => null],
		];
		$structure->behaviors = [
			'XF:Reactable' => ['stateField' => 'message_state'],
			'XF:ReactableContainer' => [
				'childContentType' => 'profile_post_comment',
				'childIds' => function ($profilePost) { return $profilePost->comment_ids; },
				'stateField' => 'message_state',
			],
			'XF:Indexable' => [
				'checkForUpdates' => ['message', 'profile_user_id', 'user_id', 'post_date', 'message_state'],
				'enqueueIndexNow' => true,
			],
			'XF:IndexableContainer' => [
				'childContentType' => 'profile_post_comment',
				'childIds' => function ($profilePost) { return $profilePost->comment_ids; },
				'checkForUpdates' => ['profile_user_id', 'message_state'],
			],
			'XF:NewsFeedPublishable' => [
				'usernameField' => 'username',
				'dateField' => 'post_date',
			],
			'XF:Webhook' => [],
		];
		$structure->getters = [
			'comment_ids' => true,
			'LatestComments' => true,
			'Unfurls' => true,
		];
		$structure->relations = [
			'ProfileUser' => [
				'entity' => 'XF:User',
				'type' => self::TO_ONE,
				'conditions' => [['user_id', '=', '$profile_user_id']],
				'primary' => true,
			],
			'User' => [
				'entity' => 'XF:User',
				'type' => self::TO_ONE,
				'conditions' => 'user_id',
				'primary' => true,
				'api' => true,
			],
			'DeletionLog' => [
				'entity' => 'XF:DeletionLog',
				'type' => self::TO_ONE,
				'conditions' => [
					['content_type', '=', 'profile_post'],
					['content_id', '=', '$profile_post_id'],
				],
				'primary' => true,
			],
			'ApprovalQueue' => [
				'entity' => 'XF:ApprovalQueue',
				'type' => self::TO_ONE,
				'conditions' => [
					['content_type', '=', 'profile_post'],
					['content_id', '=', '$profile_post_id'],
				],
				'primary' => true,
			],
			'Comments' => [
				'entity' => 'XF:ProfilePostComment',
				'type' => self::TO_MANY,
				'conditions' => 'profile_post_id',
				'primary' => true,
			],
			'Attachments' => [
				'entity' => 'XF:Attachment',
				'type' => self::TO_MANY,
				'conditions' => [
					['content_type', '=', 'profile_post'],
					['content_id', '=', '$profile_post_id'],
				],
				'with' => 'Data',
				'order' => 'attach_date',
			],
		];
		$structure->options = [
			'log_moderator' => true,
		];

		$structure->withAliases = [
			'full' => [
				'User',
				function ()
				{
					$userId = \XF::visitor()->user_id;
					if ($userId)
					{
						return 'Reactions|' . $userId;
					}

					return null;
				},
			],
			'fullProfile' => ['full', 'ProfileUser', 'ProfileUser.Privacy'],
			'api' => [
				'User',
				'User.api',
				function ($withParams)
				{
					if (!empty($withParams['profile']))
					{
						return ['ProfileUser.api'];
					}
				},
			],
		];

		static::addReactableStructureElements($structure);
		static::addEmbedRendererStructureElements($structure);
		static::addEmbedResolverStructureElements($structure);

		return $structure;
	}
}
