<?php

namespace Mautic\EmailBundle\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Events;
use Doctrine\ORM\Mapping as ORM;
use Mautic\ApiBundle\Serializer\Driver\ApiMetadataDriver;
use Mautic\AssetBundle\Entity\Asset;
use Mautic\CategoryBundle\Entity\Category;
use Mautic\CoreBundle\Doctrine\Mapping\ClassMetadataBuilder;
use Mautic\CoreBundle\Entity\DynamicContentEntityTrait;
use Mautic\CoreBundle\Entity\FormEntity;
use Mautic\CoreBundle\Entity\OptimisticLockInterface;
use Mautic\CoreBundle\Entity\OptimisticLockTrait;
use Mautic\CoreBundle\Entity\TranslationEntityInterface;
use Mautic\CoreBundle\Entity\TranslationEntityTrait;
use Mautic\CoreBundle\Entity\UuidInterface;
use Mautic\CoreBundle\Entity\UuidTrait;
use Mautic\CoreBundle\Entity\VariantEntityInterface;
use Mautic\CoreBundle\Entity\VariantEntityTrait;
use Mautic\CoreBundle\Helper\UrlHelper;
use Mautic\CoreBundle\Validator\EntityEvent;
use Mautic\EmailBundle\Validator\EmailLists;
use Mautic\EmailBundle\Validator\EmailOrEmailTokenList;
use Mautic\EmailBundle\Validator\ScheduleDateRange;
use Mautic\EmailBundle\Validator\TextOnlyDynamicContent;
use Mautic\FormBundle\Entity\Form;
use Mautic\LeadBundle\Entity\LeadList;
use Mautic\PageBundle\Entity\Page;
use Mautic\ProjectBundle\Entity\ProjectTrait;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Mapping\ClassMetadata;

#[ApiResource(
    operations: [
        new GetCollection(security: "is_granted('email:emails:viewown')"),
        new Post(security: "is_granted('email:emails:create')"),
        new Get(security: "is_granted('email:emails:viewown', object)"),
        new Put(security: "is_granted('email:emails:editown', object)"),
        new Patch(security: "is_granted('email:emails:editother', object)"),
        new Delete(security: "is_granted('email:emails:deleteown', object)"),
    ],
    normalizationContext: [
        'groups'                  => ['email:read'],
        'swagger_definition_name' => 'Read',
        'api_included'            => ['category', 'asset', 'page', 'translationChildren', 'unsubscribeForm', 'fields', 'actions', 'lists', 'excludedLists', 'preferenceCenter', 'assetAttachments', 'variantChildren'],
    ],
    denormalizationContext: [
        'groups'                  => ['email:write'],
        'swagger_definition_name' => 'Write',
    ]
)]
/**
 * @use VariantEntityTrait<Email>
 * @use TranslationEntityTrait<Email>
 */
class Email extends FormEntity implements VariantEntityInterface, TranslationEntityInterface, UuidInterface, OptimisticLockInterface
{
    use VariantEntityTrait;
    use TranslationEntityTrait;
    use DynamicContentEntityTrait;
    use UuidTrait;
    use ProjectTrait;
    use OptimisticLockTrait;

    public const ENTITY_NAME = 'email';

    public const MAX_NAME_SUBJECT_LENGTH = 190;

    /**
     * @var int
     */
    #[Groups(['email:read', 'download:read'])]
    private $id;

    /**
     * @var string
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $name;

    /**
     * @var string|null
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $description;

    /**
     * @var string|null
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $subject;

    /**
     * @var bool|null
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $useOwnerAsMailer;

    #[Groups(['email:read', 'email:write', 'download:read'])]
    private ?string $preheaderText = null;

    /**
     * @var string|null
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $fromAddress;

    /**
     * @var string|null
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $fromName;

    /**
     * @var string|null
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $replyToAddress;

    /**
     * @var string|null
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $bccAddress;

    /**
     * @var string|null
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $template;

    /**
     * @var array
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $content = [];

    /**
     * @var array
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $utmTags = [];

    /**
     * @var string|null
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $plainText;

    /**
     * @var string|null
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $customHtml;

    /**
     * @var string|null
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $emailType = 'template';

    /**
     * @var \DateTimeInterface|null
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $publishUp;

    /**
     * @var \DateTimeInterface|null
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $publishDown;

    /**
     * @var bool|null
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $publicPreview = false;

    /**
     * @var int
     */
    #[Groups(['email:read', 'download:read'])]
    private $readCount = 0;

    /**
     * @var int
     */
    #[Groups(['email:read', 'download:read'])]
    private $sentCount = 0;

    /**
     * @var int
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $revision = 1;

    /**
     * @var Category|null
     **/
    #[Groups(['email:read', 'email:write'])]
    private $category;

    /**
     * @var ArrayCollection<LeadList>
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $lists;

    /**
     * @var ArrayCollection<LeadList>
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $excludedLists;

    /**
     * @var ArrayCollection<Stat>
     */
    private $stats;

    /**
     * @var int
     */
    #[Groups(['email:read', 'download:read'])]
    private $variantSentCount = 0;

    /**
     * @var int
     */
    #[Groups(['email:read', 'download:read'])]
    private $variantReadCount = 0;

    /**
     * @var Form|null
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $unsubscribeForm;

    /**
     * @var Page|null
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $preferenceCenter;

    /**
     * @var ArrayCollection<Asset>
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $assetAttachments;

    /**
     * Used to identify the page for the builder.
     */
    private $sessionId;

    /**
     * @var array
     */
    #[Groups(['email:read', 'email:write', 'download:read'])]
    private $headers = [];

    /**
     * @var int
     */
    private $pendingCount = 0;

    /**
     * @var int
     */
    #[Groups(['email:read', 'download:read'])]
    private $queuedCount = 0;

    private ?EmailDraft $draft = null;

    private bool $isCloned = false;

    /**
     * In some use cases, we need to get the original email ID after it's been cloned.
     *
     * @var int
     */
    private $clonedId;

    #[Groups(['email:read', 'email:write', 'download:read'])]
    private bool $continueSending = false;

    #[Groups(['email:read', 'email:write', 'download:read'])]
    private bool $isDuplicate = false;

    public function __clone()
    {
        $this->isCloned          = true;
        $this->clonedId          = $this->id;
        $this->id                = null;
        $this->sentCount         = 0;
        $this->readCount         = 0;
        $this->revision          = 0;
        $this->variantSentCount  = 0;
        $this->variantReadCount  = 0;
        $this->variantStartDate  = null;
        $this->emailType         = null;
        $this->sessionId         = 'new_'.hash('sha1', uniqid(mt_rand()));
        $this->plainText         = null;
        $this->publishUp         = null;
        $this->publishDown       = null;
        $this->continueSending   = false;
        $this->clearTranslations();
        $this->clearVariants();
        $this->clearStats();
        $this->setDraft(null);

        parent::__clone();
    }

    public function __construct()
    {
        $this->lists               = new ArrayCollection();
        $this->excludedLists       = new ArrayCollection();
        $this->stats               = new ArrayCollection();
        $this->translationChildren = new ArrayCollection();
        $this->variantChildren     = new ArrayCollection();
        $this->assetAttachments    = new ArrayCollection();
        $this->setDateAdded(new \DateTime());
        $this->setDateModified(new \DateTime());
        $this->initializeProjects();
    }

    public function clearStats(): void
    {
        $this->stats = new ArrayCollection();
    }

    public static function loadMetadata(ORM\ClassMetadata $metadata): void
    {
        $builder = new ClassMetadataBuilder($metadata);

        $builder->setTable('emails')
            ->setCustomRepositoryClass(EmailRepository::class)
            ->addLifecycleEvent('cleanUrlsInContent', Events::preUpdate)
            ->addLifecycleEvent('cleanUrlsInContent', Events::prePersist);

        $builder->addIdColumns();
        $builder->addNullableField('subject', Types::TEXT);
        $builder->addNullableField('preheaderText', Types::STRING, 'preheader_text');
        $builder->addNullableField('fromAddress', Types::STRING, 'from_address');
        $builder->addNullableField('fromName', Types::STRING, 'from_name');
        $builder->addNullableField('replyToAddress', Types::STRING, 'reply_to_address');
        $builder->addNullableField('bccAddress', Types::STRING, 'bcc_address');
        $builder->addNullableField('useOwnerAsMailer', Types::BOOLEAN, 'use_owner_as_mailer');
        $builder->addNullableField('template', Types::STRING);
        $builder->addNullableField('content', Types::ARRAY);
        $builder->addNullableField('utmTags', Types::ARRAY, 'utm_tags');
        $builder->addNullableField('plainText', Types::TEXT, 'plain_text');
        $builder->addNullableField('customHtml', Types::TEXT, 'custom_html');
        $builder->addNullableField('emailType', Types::TEXT, 'email_type');
        $builder->addPublishDates();
        $builder->addField('continueSending', Types::BOOLEAN, [
            'columnName' => 'continue_sending',
            'nullable'   => false,
            'options'    => [
                'default' => false,
            ],
        ]);
        $builder->addNamedField('readCount', Types::INTEGER, 'read_count');
        $builder->addNamedField('sentCount', Types::INTEGER, 'sent_count');
        $builder->addNamedField('variantSentCount', Types::INTEGER, 'variant_sent_count');
        $builder->addNamedField('variantReadCount', Types::INTEGER, 'variant_read_count');
        $builder->addField('revision', Types::INTEGER);
        $builder->addCategory();

        $builder->createManyToMany('lists', LeadList::class)
            ->setJoinTable('email_list_xref')
            ->setIndexBy('id')
            ->addInverseJoinColumn('leadlist_id', 'id', false, false, 'CASCADE')
            ->addJoinColumn('email_id', 'id', false, false, 'CASCADE')
            ->fetchExtraLazy()
            ->build();

        $builder->createManyToMany('excludedLists', LeadList::class)
            ->setJoinTable('email_list_excluded')
            ->setIndexBy('id')
            ->addInverseJoinColumn('leadlist_id', 'id', false, false, 'CASCADE')
            ->addJoinColumn('email_id', 'id', false, false, 'CASCADE')
            ->fetchExtraLazy()
            ->build();

        $builder->createOneToMany('stats', 'Stat')
            ->setIndexBy('id')
            ->mappedBy('email')
            ->cascadePersist()
            ->fetchExtraLazy()
            ->build();

        self::addTranslationMetadata($builder, self::class);
        self::addVariantMetadata($builder, self::class);
        self::addDynamicContentMetadata($builder);

        $builder->createManyToOne('unsubscribeForm', Form::class)
            ->addJoinColumn('unsubscribeform_id', 'id', true, false, 'SET NULL')
            ->build();

        $builder->createManyToOne('preferenceCenter', Page::class)
            ->addJoinColumn('preference_center_id', 'id', true, false, 'SET NULL')
            ->build();

        $builder->createManyToMany('assetAttachments', Asset::class)
            ->setJoinTable('email_assets_xref')
            ->addInverseJoinColumn('asset_id', 'id', false, false, 'CASCADE')
            ->addJoinColumn('email_id', 'id', false, false, 'CASCADE')
            ->fetchExtraLazy()
            ->build();

        $builder->addField('headers', Types::JSON);

        $builder->addNullableField('publicPreview', Types::BOOLEAN, 'public_preview');

        $builder->createOneToOne('draft', EmailDraft::class)
            ->mappedBy('email')
            ->fetchExtraLazy()
            ->cascadeAll()
            ->build();

        static::addUuidField($builder);
        self::addProjectsField($builder, 'email_projects_xref', 'email_id');
        self::addVersionField($builder);
    }

    public static function loadValidatorMetadata(ClassMetadata $metadata): void
    {
        $metadata->addPropertyConstraint(
            'name',
            new NotBlank(
                [
                    'message' => 'mautic.core.name.required',
                ]
            )
        );

        $metadata->addPropertyConstraint(
            'name',
            new Length(
                [
                    'max'        => self::MAX_NAME_SUBJECT_LENGTH,
                    'maxMessage' => 'mautic.email.name.length',
                ]
            )
        );

        $metadata->addPropertyConstraint(
            'subject',
            new NotBlank(
                [
                    'message' => 'mautic.core.subject.required',
                ]
            )
        );

        $metadata->addPropertyConstraint(
            'subject',
            new Length(
                [
                    'max'        => self::MAX_NAME_SUBJECT_LENGTH,
                    'maxMessage' => 'mautic.email.subject.length',
                ]
            )
        );

        $metadata->addPropertyConstraint(
            'preheaderText',
            new Length(
                [
                    'max'        => 130,
                    'maxMessage' => 'mautic.email.preheader_text.length',
                ]
            )
        );

        $metadata->addPropertyConstraint(
            'fromAddress',
            new EmailOrEmailTokenList(),
        );

        $metadata->addPropertyConstraint(
            'replyToAddress',
            new \Symfony\Component\Validator\Constraints\Email(
                [
                    'message' => 'mautic.core.email.required',
                ]
            )
        );

        $metadata->addPropertyConstraint(
            'bccAddress',
            new \Symfony\Component\Validator\Constraints\Email(
                [
                    'message' => 'mautic.core.email.required',
                ]
            )
        );

        $metadata->addPropertyConstraint('subject', new TextOnlyDynamicContent());

        $metadata->addConstraint(new EmailLists());
        $metadata->addConstraint(new EntityEvent());
        $metadata->addConstraint(new ScheduleDateRange());

        $metadata->addConstraint(new Callback(
            function (Email $email, ExecutionContextInterface $context): void {
                if ($email->isVariant()) {
                    // Get a summation of weights
                    $parent   = $email->getVariantParent();
                    $children = $parent ? $parent->getVariantChildren() : $email->getVariantChildren();

                    $total = 0;
                    foreach ($children as $child) {
                        $settings = $child->getVariantSettings();
                        $total += (int) $settings['weight'];
                    }

                    if ($total > 100) {
                        $context->buildViolation('mautic.core.variant_weights_invalid')
                            ->atPath('variantSettings[weight]')
                            ->addViolation();
                    }
                }
            },
        ));
    }

    /**
     * Prepares the metadata for API usage.
     */
    public static function loadApiMetadata(ApiMetadataDriver $metadata): void
    {
        $metadata->setGroupPrefix('email')
            ->addListProperties(
                [
                    'id',
                    'name',
                    'subject',
                    'language',
                    'category',
                ]
            )
            ->addProperties(
                [
                    'fromAddress',
                    'fromName',
                    'replyToAddress',
                    'bccAddress',
                    'useOwnerAsMailer',
                    'utmTags',
                    'preheaderText',
                    'customHtml',
                    'plainText',
                    'template',
                    'emailType',
                    'publishUp',
                    'publishDown',
                    'publicPreview',
                    'readCount',
                    'sentCount',
                    'revision',
                    'assetAttachments',
                    'variantStartDate',
                    'variantSentCount',
                    'variantReadCount',
                    'variantParent',
                    'variantChildren',
                    'translationParent',
                    'translationChildren',
                    'unsubscribeForm',
                    'dynamicContent',
                    'lists',
                    'headers',
                ]
            )
            ->build();

        self::addProjectsInLoadApiMetadata($metadata, 'email');
    }

    protected function isChanged($prop, $val)
    {
        $getter  = 'get'.ucfirst($prop);
        $current = $this->$getter();

        if ('variantParent' == $prop || 'translationParent' == $prop || 'category' == $prop || 'list' == $prop) {
            $currentId = ($current) ? $current->getId() : '';
            $newId     = ($val) ? $val->getId() : null;
            if ($currentId != $newId) {
                $this->changes[$prop] = [$currentId, $newId];
            }
        } else {
            parent::isChanged($prop, $val);
        }
    }

    /**
     * @return mixed
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param ?string $name
     *
     * @return $this
     */
    public function setName($name)
    {
        $this->isChanged('name', $name);
        $this->name = $name;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * @param ?string $description
     *
     * @return Email
     */
    public function setDescription($description)
    {
        $this->isChanged('description', $description);
        $this->description = $description;

        return $this;
    }

    public function setId(int $id): Email
    {
        $this->id = $id;

        return $this;
    }

    /**
     * @return int|null
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return ?Category
     */
    public function getCategory()
    {
        return $this->category;
    }

    /**
     * @return $this
     */
    public function setCategory($category)
    {
        $this->isChanged('category', $category);
        $this->category = $category;

        return $this;
    }

    /**
     * @return array
     */
    public function getContent()
    {
        return $this->content;
    }

    /**
     * @return $this
     */
    public function setContent($content)
    {
        $this->isChanged('content', $content);
        $this->content = $content;

        return $this;
    }

    /**
     * @return array
     */
    public function getUtmTags()
    {
        return $this->utmTags;
    }

    /**
     * @param array $utmTags
     */
    public function setUtmTags($utmTags)
    {
        $this->isChanged('utmTags', $utmTags);
        $this->utmTags = $utmTags;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getReadCount($includeVariants = false)
    {
        return ($includeVariants) ? $this->getAccumulativeVariantCount('getReadCount') : $this->readCount;
    }

    /**
     * @return $this
     */
    public function setReadCount($readCount)
    {
        $this->readCount = $readCount;

        return $this;
    }

    public function getIsClone(): bool
    {
        return $this->isCloned;
    }

    /**
     * @return mixed
     */
    public function getRevision()
    {
        return $this->revision;
    }

    /**
     * @return $this
     */
    public function setRevision($revision)
    {
        $this->revision = $revision;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getSessionId()
    {
        return $this->sessionId;
    }

    /**
     * @return $this
     */
    public function setSessionId($sessionId)
    {
        $this->sessionId = $sessionId;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getSubject()
    {
        return $this->subject;
    }

    /**
     * @return $this
     */
    public function setSubject($subject)
    {
        $this->isChanged('subject', $subject);
        $this->subject = $subject;

        return $this;
    }

    /**
     * @return ?bool
     */
    public function getUseOwnerAsMailer()
    {
        return $this->useOwnerAsMailer;
    }

    /**
     * @param bool $useOwnerAsMailer
     *
     * @return $this
     */
    public function setUseOwnerAsMailer($useOwnerAsMailer)
    {
        $this->useOwnerAsMailer = $useOwnerAsMailer;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getFromAddress()
    {
        return $this->fromAddress;
    }

    /**
     * @param mixed $fromAddress
     *
     * @return Email
     */
    public function setFromAddress($fromAddress)
    {
        $this->isChanged('fromAddress', $fromAddress);
        $this->fromAddress = $fromAddress;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getFromName()
    {
        return $this->fromName;
    }

    /**
     * @param mixed $fromName
     *
     * @return Email
     */
    public function setFromName($fromName)
    {
        $this->isChanged('fromName', $fromName);
        $this->fromName = $fromName;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getReplyToAddress()
    {
        return $this->replyToAddress;
    }

    /**
     * @param mixed $replyToAddress
     *
     * @return Email
     */
    public function setReplyToAddress($replyToAddress)
    {
        $this->isChanged('replyToAddress', $replyToAddress);
        $this->replyToAddress = $replyToAddress;

        return $this;
    }

    public function getPreheaderText(): ?string
    {
        return $this->preheaderText;
    }

    public function setPreheaderText(?string $preheaderText): Email
    {
        $this->isChanged('preheaderText', $preheaderText);
        $this->preheaderText = $preheaderText;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getBccAddress()
    {
        return $this->bccAddress;
    }

    /**
     * @param mixed $bccAddress
     *
     * @return Email
     */
    public function setBccAddress($bccAddress)
    {
        $this->isChanged('bccAddress', $bccAddress);
        $this->bccAddress = $bccAddress;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getTemplate()
    {
        return $this->template;
    }

    /**
     * @return $this
     */
    public function setTemplate($template)
    {
        $this->isChanged('template', $template);
        $this->template = $template;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getPublishDown()
    {
        if ($this->isSegmentEmail() && !$this->isContinueSending()) {
            return null;
        }

        return $this->publishDown;
    }

    /**
     * @return $this
     */
    public function setPublishDown($publishDown)
    {
        $this->isChanged('publishDown', $publishDown);
        $this->publishDown = $publishDown;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getPublishUp()
    {
        return $this->publishUp;
    }

    /**
     * @return $this
     */
    public function setPublishUp($publishUp)
    {
        $this->isChanged('publishUp', $publishUp);
        $this->publishUp = $publishUp;

        return $this;
    }

    /**
     * @param bool $includeVariants
     *
     * @return mixed
     */
    public function getSentCount($includeVariants = false)
    {
        return ($includeVariants) ? $this->getAccumulativeVariantCount('getSentCount') : $this->sentCount;
    }

    /**
     * @return $this
     */
    public function setSentCount($sentCount)
    {
        $this->sentCount = $sentCount;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getVariantSentCount($includeVariants = false)
    {
        return ($includeVariants) ? $this->getAccumulativeVariantCount('getVariantSentCount') : $this->variantSentCount;
    }

    /**
     * @return $this
     */
    public function setVariantSentCount($variantSentCount)
    {
        $this->variantSentCount = $variantSentCount;

        return $this;
    }

    /**
     * @return ArrayCollection<int, LeadList>
     */
    public function getLists()
    {
        return $this->lists;
    }

    /**
     * Add list.
     *
     * @return Email
     */
    public function addList(LeadList $list)
    {
        $this->listsChangedAdd('lists', $list->getId());
        $this->lists[] = $list;

        return $this;
    }

    /**
     * Set the lists for this translation.
     */
    public function setLists(array $lists = [])
    {
        $lists = new ArrayCollection($lists);
        $this->listsChangedSet('lists', $this->getListKeys($lists));
        $this->lists = $lists;

        return $this;
    }

    /**
     * Remove list.
     */
    public function removeList(LeadList $list): void
    {
        $this->listsChangedRemove('lists', $list->getId());
        $this->lists->removeElement($list);
    }

    /**
     * @return Collection<int, LeadList>
     */
    public function getExcludedLists(): Collection
    {
        return $this->excludedLists;
    }

    public function addExcludedList(LeadList $excludedList): void
    {
        $this->listsChangedAdd('excludedLists', $excludedList->getId());
        $this->excludedLists->add($excludedList);
    }

    public function removeExcludedList(LeadList $excludedList): void
    {
        $this->listsChangedRemove('excludedLists', $excludedList->getId());
        $this->excludedLists->removeElement($excludedList);
    }

    /**
     * @return mixed
     */
    public function getPlainText()
    {
        return $this->plainText;
    }

    /**
     * @return $this
     */
    public function setPlainText($plainText)
    {
        $this->plainText = $plainText;

        return $this;
    }

    public function isContinueSending(): bool
    {
        return $this->continueSending;
    }

    public function getContinueSending(): bool
    {
        return $this->continueSending;
    }

    public function setContinueSending(bool $continueSending): self
    {
        $this->isChanged('continueSending', $continueSending);
        $this->continueSending = $continueSending;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getVariantReadCount()
    {
        return $this->variantReadCount;
    }

    /**
     * @return $this
     */
    public function setVariantReadCount($variantReadCount)
    {
        $this->variantReadCount = $variantReadCount;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getStats()
    {
        return $this->stats;
    }

    /**
     * @return mixed
     */
    public function getCustomHtml()
    {
        return $this->customHtml;
    }

    /**
     * @return $this
     */
    public function setCustomHtml($customHtml)
    {
        $this->customHtml = $customHtml;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getUnsubscribeForm()
    {
        return $this->unsubscribeForm;
    }

    /**
     * @return $this
     */
    public function setUnsubscribeForm(?Form $unsubscribeForm = null)
    {
        $this->unsubscribeForm = $unsubscribeForm;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getPreferenceCenter()
    {
        return $this->preferenceCenter;
    }

    /**
     * @return $this
     */
    public function setPreferenceCenter(?Page $preferenceCenter = null)
    {
        $this->isChanged('preferenceCenter', $preferenceCenter);
        $this->preferenceCenter = $preferenceCenter;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getEmailType()
    {
        return $this->emailType;
    }

    /**
     * @param mixed $emailType
     *
     * @return Email
     */
    public function setEmailType($emailType)
    {
        $this->emailType = $emailType;

        return $this;
    }

    /**
     * Add asset.
     *
     * @return Email
     */
    public function addAssetAttachment(Asset $asset)
    {
        $this->assetAttachments[] = $asset;

        return $this;
    }

    /**
     * Remove asset.
     */
    public function removeAssetAttachment(Asset $asset): void
    {
        $this->assetAttachments->removeElement($asset);
    }

    /**
     * Get assetAttachments.
     *
     * @return Collection
     */
    public function getAssetAttachments()
    {
        return $this->assetAttachments;
    }

    /**
     * @return array
     */
    public function getHeaders()
    {
        return $this->headers;
    }

    /**
     * @param array $headers
     *
     * @return Email
     */
    public function setHeaders($headers)
    {
        $this->headers = $headers;

        return $this;
    }

    /**
     * Lifecycle callback to clean URLs in the content.
     */
    public function cleanUrlsInContent(): void
    {
        if (is_string($this->plainText)) {
            $this->decodeAmpersands($this->plainText);
        }

        if (is_string($this->customHtml)) {
            $this->decodeAmpersands($this->customHtml);
        }
    }

    /**
     * Check all links in content and decode ampersands.
     */
    private function decodeAmpersands(string &$content): void
    {
        if (preg_match_all('/((https?|ftps?):\/\/)([a-zA-Z0-9-\.{}]*[a-zA-Z0-9=}]*)(\??)([^\s\"\]]+)?/i', $content, $matches)) {
            foreach ($matches[0] as $url) {
                $content = str_replace($url, UrlHelper::decodeAmpersands($url), $content);
            }
        }
    }

    /**
     * Calculate Read Percentage for each Email.
     */
    public function getReadPercentage($includevariants = false): float|int
    {
        if ($this->getSentCount($includevariants) > 0) {
            return round($this->getReadCount($includevariants) / $this->getSentCount($includevariants) * 100, 2);
        }

        return 0;
    }

    /**
     * @return bool
     */
    public function getPublicPreview()
    {
        return $this->publicPreview;
    }

    /**
     * @return bool
     */
    public function isPublicPreview()
    {
        return $this->publicPreview;
    }

    /**
     * @param bool $publicPreview
     *
     * @return $this
     */
    public function setPublicPreview($publicPreview)
    {
        $this->isChanged('publicPreview', $publicPreview);
        $this->publicPreview = $publicPreview;

        return $this;
    }

    /**
     * @param int $count
     *
     * @return $this
     */
    public function setQueuedCount($count)
    {
        $this->queuedCount = $count;

        return $this;
    }

    /**
     * @return int
     */
    public function getQueuedCount()
    {
        return $this->queuedCount;
    }

    /**
     * @param int $count
     *
     * @return $this
     */
    public function setPendingCount($count)
    {
        $this->pendingCount = $count;

        return $this;
    }

    /**
     * @return int
     */
    public function getPendingCount()
    {
        return $this->pendingCount;
    }

    public function getClonedId(): ?int
    {
        return $this->clonedId;
    }

    public function isBackgroundSending(): bool
    {
        return $this->isPublished() && !empty($this->getPublishUp()) && ($this->getPublishUp() < new \DateTime());
    }

    private function listsChangedAdd(string $property, ?int $id): void
    {
        $this->initListChanges($property);
        $this->changes[$property][1] = array_unique(array_merge($this->changes[$property][1], [$id]));
    }

    private function listsChangedRemove(string $property, ?int $id): void
    {
        $this->initListChanges($property);
        $this->changes[$property][1] = array_diff($this->changes[$property][1], [$id]);
    }

    public function getDraft(): ?EmailDraft
    {
        return $this->draft;
    }

    public function setDraft(?EmailDraft $draft): void
    {
        $this->draft = $draft;
    }

    public function hasDraft(): bool
    {
        return null !== $this->getDraft();
    }

    public function getDraftContent(): ?string
    {
        return $this->getDraft()?->getHtml();
    }

    /**
     * @param mixed[] $ids
     */
    private function listsChangedSet(string $property, array $ids): void
    {
        $this->initListChanges($property);
        $this->changes[$property][1] = $ids;
    }

    private function initListChanges(string $property): void
    {
        if (!isset($this->changes[$property])) {
            $list                     = $this->$property;
            $current                  = $this->getListKeys($list);
            $this->changes[$property] = [$current, $current];
        }
    }

    /**
     * @param iterable<mixed> $list
     *
     * @return mixed[]
     */
    private function getListKeys(iterable $list): array
    {
        $keys = [];

        foreach ($list as $key => $value) {
            $keys[] = $key;
        }

        return $keys;
    }

    public function isSegmentEmail(): bool
    {
        return 'list' === $this->getEmailType();
    }

    public function getSendingStatus(): string
    {
        $publishStatus = $this->getPublishStatus();

        switch ($publishStatus) {
            case 'published':
            case 'unpublished':
                if ($this->isSegmentEmail() && $this->getIsPublished()) {
                    if (!$this->isContinueSending() && !$this->getPendingCount() && $this->getSentCount(true)) {
                        return 'sent';
                    }

                    if ($this->getPendingCount()) {
                        return 'sending';
                    }
                }
                break;
        }

        return $publishStatus;
    }

    public function shouldCheckForUnpublishEmail(): bool
    {
        if ($this->isContinueSending()) {
            return false;
        }

        if (empty($this->getSentCount(true))) {
            return false;
        }

        return true;
    }

    public function isDuplicate(): bool
    {
        return $this->isDuplicate;
    }

    public function setIsDuplicate(bool $isDuplicate): void
    {
        $this->isDuplicate = $isDuplicate;
    }
}
