<?php

namespace Mautic\LeadBundle\Model;

use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Schema\SchemaException;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Tools\Pagination\Paginator;
use Mautic\CoreBundle\Cache\ResultCacheOptions;
use Mautic\CoreBundle\Doctrine\Helper\ColumnSchemaHelper;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\CoreBundle\Helper\InputHelper;
use Mautic\CoreBundle\Helper\UserHelper;
use Mautic\CoreBundle\Model\FormModel;
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
use Mautic\CoreBundle\Translation\Translator;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Entity\LeadField;
use Mautic\LeadBundle\Entity\LeadFieldRepository;
use Mautic\LeadBundle\Entity\LeadRepository;
use Mautic\LeadBundle\Event\LeadFieldEvent;
use Mautic\LeadBundle\Exception\NoListenerException;
use Mautic\LeadBundle\Field\CustomFieldColumn;
use Mautic\LeadBundle\Field\Dispatcher\FieldSaveDispatcher;
use Mautic\LeadBundle\Field\Exception\AbortColumnCreateException;
use Mautic\LeadBundle\Field\Exception\AbortColumnUpdateException;
use Mautic\LeadBundle\Field\Exception\CustomFieldLimitException;
use Mautic\LeadBundle\Field\FieldList;
use Mautic\LeadBundle\Field\LeadFieldDeleter;
use Mautic\LeadBundle\Field\LeadFieldSaver;
use Mautic\LeadBundle\Form\Type\FieldType;
use Mautic\LeadBundle\Helper\FormFieldHelper;
use Mautic\LeadBundle\LeadEvents;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\EventDispatcher\Event;

/**
 * @extends FormModel<LeadField>
 */
class FieldModel extends FormModel
{
    public static $coreFields = [
        // Listed according to $order for installation
        'title' => [
            'type'       => 'lookup',
            'properties' => [
                'list' => [
                    'Mr',
                    'Mrs',
                    'Miss',
                ],
            ],
            'fixed'    => true,
            'listable' => true,
            'object'   => 'lead',
        ],
        'firstname' => [
            'fixed'    => true,
            'short'    => true,
            'listable' => true,
            'object'   => 'lead',
        ],
        'lastname' => [
            'fixed'    => true,
            'short'    => true,
            'listable' => true,
            'object'   => 'lead',
        ],
        'company' => [
            'fixed'    => true,
            'listable' => true,
            'object'   => 'lead',
        ],
        'position' => [
            'fixed'    => true,
            'listable' => true,
            'object'   => 'lead',
        ],
        'email' => [
            'type'     => 'email',
            'unique'   => true,
            'fixed'    => true,
            'short'    => true,
            'listable' => true,
            'object'   => 'lead',
        ],
        'mobile' => [
            'type'     => 'tel',
            'fixed'    => true,
            'listable' => true,
            'object'   => 'lead',
        ],
        'phone' => [
            'type'     => 'tel',
            'fixed'    => true,
            'listable' => true,
            'object'   => 'lead',
        ],
        'points' => [
            'type'     => 'number',
            'fixed'    => true,
            'listable' => true,
            'object'   => 'lead',
            'default'  => 0,
        ],
        'fax' => [
            'type'     => 'tel',
            'listable' => true,
            'object'   => 'lead',
        ],
        'address1' => [
            'fixed'    => true,
            'listable' => true,
            'object'   => 'lead',
        ],
        'address2' => [
            'fixed'    => true,
            'listable' => true,
            'object'   => 'lead',
        ],
        'city' => [
            'fixed'    => true,
            'listable' => true,
            'object'   => 'lead',
        ],
        'state' => [
            'type'     => 'region',
            'fixed'    => true,
            'listable' => true,
            'object'   => 'lead',
        ],
        'zipcode' => [
            'fixed'    => true,
            'listable' => true,
            'object'   => 'lead',
        ],
        'country' => [
            'type'     => 'country',
            'fixed'    => true,
            'listable' => true,
            'object'   => 'lead',
        ],
        'preferred_locale' => [
            'type'     => 'locale',
            'fixed'    => true,
            'listable' => true,
            'object'   => 'lead',
        ],
        'timezone' => [
            'type'     => 'timezone',
            'fixed'    => true,
            'listable' => true,
            'object'   => 'lead',
        ],
        'last_active' => [
            'type'     => 'datetime',
            'fixed'    => true,
            'listable' => true,
            'object'   => 'lead',
        ],
        'attribution_date' => [
            'type'     => 'datetime',
            'fixed'    => true,
            'listable' => true,
            'object'   => 'lead',
        ],
        'attribution' => [
            'type'       => 'number',
            'properties' => ['roundmode' => 4, 'scale' => 2],
            'fixed'      => true,
            'listable'   => true,
            'object'     => 'lead',
        ],
        'website' => [
            'type'     => 'url',
            'listable' => true,
            'object'   => 'lead',
        ],
        'facebook' => [
            'listable' => true,
            'group'    => 'social',
            'object'   => 'lead',
        ],
        'foursquare' => [
            'listable' => true,
            'group'    => 'social',
            'object'   => 'lead',
        ],
        'instagram' => [
            'listable' => true,
            'group'    => 'social',
            'object'   => 'lead',
        ],
        'linkedin' => [
            'listable' => true,
            'group'    => 'social',
            'object'   => 'lead',
        ],
        'skype' => [
            'listable' => true,
            'group'    => 'social',
            'object'   => 'lead',
        ],
        'twitter' => [
            'listable' => true,
            'group'    => 'social',
            'object'   => 'lead',
        ],
    ];

    public static $coreCompanyFields = [
        // Listed according to $order for installation
        'companyaddress1' => [
            'fixed'    => true,
            'listable' => true,
            'object'   => 'company',
        ],
        'companyaddress2' => [
            'fixed'    => true,
            'listable' => true,
            'object'   => 'company',
        ],
        'companyemail' => [
            'type'     => 'email',
            'fixed'    => true,
            'listable' => true,
            'object'   => 'company',
        ],
        'companyphone' => [
            'type'     => 'tel',
            'fixed'    => true,
            'listable' => true,
            'object'   => 'company',
        ],
        'companycity' => [
            'fixed'    => true,
            'listable' => true,
            'object'   => 'company',
        ],
        'companystate' => [
            'type'     => 'region',
            'fixed'    => true,
            'listable' => true,
            'object'   => 'company',
        ],
        'companyzipcode' => [
            'fixed'    => true,
            'listable' => true,
            'object'   => 'company',
        ],
        'companycountry' => [
            'type'     => 'country',
            'fixed'    => true,
            'listable' => true,
            'object'   => 'company',
        ],
        'companyname' => [
            'fixed'    => true,
            'required' => true,
            'listable' => true,
            'unique'   => true,
            'object'   => 'company',
        ],
        'companywebsite' => [
            'fixed'    => true,
            'type'     => 'url',
            'listable' => true,
            'object'   => 'company',
        ],
        'companynumber_of_employees' => [
            'type'       => 'number',
            'properties' => ['roundmode' => 4, 'scale' => 0],
            'group'      => 'professional',
            'listable'   => true,
            'object'     => 'company',
        ],
        'companyfax' => [
            'type'     => 'tel',
            'listable' => true,
            'group'    => 'professional',
            'object'   => 'company',
        ],
        'companyannual_revenue' => [
            'type'       => 'number',
            'properties' => ['roundmode' => 4, 'scale' => 2],
            'listable'   => true,
            'group'      => 'professional',
            'object'     => 'company',
        ],
        'companyindustry' => [
            'type'       => 'select',
            'group'      => 'professional',
            'properties' => [
                'list' => [
                    [
                        'label' => 'Aerospace & Defense',
                        'value' => 'Aerospace & Defense',
                    ],
                    [
                        'label' => 'Agriculture',
                        'value' => 'Agriculture',
                    ],
                    [
                        'label' => 'Apparel',
                        'value' => 'Apparel',
                    ],
                    [
                        'label' => 'Automotive & Assembly',
                        'value' => 'Automotive & Assembly',
                    ],
                    [
                        'label' => 'Banking',
                        'value' => 'Banking',
                    ],
                    [
                        'label' => 'Biotechnology',
                        'value' => 'Biotechnology',
                    ],
                    [
                        'label' => 'Chemicals',
                        'value' => 'Chemicals',
                    ],
                    [
                        'label' => 'Communications',
                        'value' => 'Communications',
                    ],
                    [
                        'label' => 'Construction',
                        'value' => 'Construction',
                    ],
                    [
                        'label' => 'Consumer Packaged Goods',
                        'value' => 'Consumer Packaged Goods',
                    ],
                    [
                        'label' => 'Education',
                        'value' => 'Education',
                    ],
                    [
                        'label' => 'Electronics',
                        'value' => 'Electronics',
                    ],
                    [
                        'label' => 'Energy',
                        'value' => 'Energy',
                    ],
                    [
                        'label' => 'Engineering',
                        'value' => 'Engineering',
                    ],
                    [
                        'label' => 'Entertainment',
                        'value' => 'Entertainment',
                    ],
                    [
                        'label' => 'Environmental',
                        'value' => 'Environmental',
                    ],
                    [
                        'label' => 'Finance',
                        'value' => 'Finance',
                    ],
                    [
                        'label' => 'Food & Beverage',
                        'value' => 'Food & Beverage',
                    ],
                    [
                        'label' => 'Government',
                        'value' => 'Government',
                    ],
                    [
                        'label' => 'Healthcare',
                        'value' => 'Healthcare',
                    ],
                    [
                        'label' => 'Hospitality',
                        'value' => 'Hospitality',
                    ],
                    [
                        'label' => 'Insurance',
                        'value' => 'Insurance',
                    ],
                    [
                        'label' => 'Machinery',
                        'value' => 'Machinery',
                    ],
                    [
                        'label' => 'Manufacturing',
                        'value' => 'Manufacturing',
                    ],
                    [
                        'label' => 'Media',
                        'value' => 'Media',
                    ],
                    [
                        'label' => 'Metals & Mining',
                        'value' => 'Metals & Mining',
                    ],
                    [
                        'label' => 'Not for Profit',
                        'value' => 'Not for Profit',
                    ],
                    [
                        'label' => 'Oil & Gas',
                        'value' => 'Oil & Gas',
                    ],
                    [
                        'label' => 'Packaging & Paper',
                        'value' => 'Packaging & Paper',
                    ],
                    [
                        'label' => 'Private Equity & Principal Investors',
                        'value' => 'Private Equity & Principal Investors',
                    ],
                    [
                        'label' => 'Recreation',
                        'value' => 'Recreation',
                    ],
                    [
                        'label' => 'Real Estate',
                        'value' => 'Real Estate',
                    ],
                    [
                        'label' => 'Retail',
                        'value' => 'Retail',
                    ],
                    [
                        'label' => 'Semiconductors',
                        'value' => 'Semiconductors',
                    ],
                    [
                        'label' => 'Shipping',
                        'value' => 'Shipping',
                    ],
                    [
                        'label' => 'Social Sector',
                        'value' => 'Social Sector',
                    ],
                    [
                        'label' => 'Technology',
                        'value' => 'Technology',
                    ],
                    [
                        'label' => 'Telecommunications',
                        'value' => 'Telecommunications',
                    ],
                    [
                        'label' => 'Transportation',
                        'value' => 'Transportation',
                    ],
                    [
                        'label' => 'Utilities',
                        'value' => 'Utilities',
                    ],
                    [
                        'label' => 'Other',
                        'value' => 'Other',
                    ],
                ],
            ],
            'fixed'    => true,
            'listable' => true,
            'object'   => 'company',
        ],
        'companydescription' => [
            'fixed'    => true,
            'group'    => 'professional',
            'listable' => true,
            'object'   => 'company',
        ],
    ];

    public function __construct(
        private ColumnSchemaHelper $columnSchemaHelper,
        private ListModel $leadListModel,
        private CustomFieldColumn $customFieldColumn,
        private FieldSaveDispatcher $fieldSaveDispatcher,
        private LeadFieldRepository $leadFieldRepository,
        private FieldList $fieldList,
        private LeadFieldSaver $leadFieldSaver,
        private LeadFieldDeleter $leadFieldDeleter,
        EntityManagerInterface $em,
        CorePermissions $security,
        EventDispatcherInterface $dispatcher,
        UrlGeneratorInterface $router,
        Translator $translator,
        UserHelper $userHelper,
        LoggerInterface $mauticLogger,
        CoreParametersHelper $coreParametersHelper,
    ) {
        parent::__construct($em, $security, $dispatcher, $router, $translator, $userHelper, $mauticLogger, $coreParametersHelper);
    }

    public function getRepository(): LeadFieldRepository
    {
        return $this->leadFieldRepository;
    }

    public function getPermissionBase(): string
    {
        return 'lead:fields';
    }

    /**
     * Get a specific entity or generate a new one if id is empty.
     */
    public function getEntity($id = null): ?LeadField
    {
        if (null === $id) {
            return new LeadField();
        }

        return parent::getEntity($id);
    }

    /**
     * @return array
     */
    public function getLeadFields()
    {
        return $this->getEntities([
            'filter' => [
                'force' => [
                    [
                        'column' => 'f.object',
                        'expr'   => 'like',
                        'value'  => 'lead',
                    ],
                ],
            ],
        ]);
    }

    /**
     * @return LeadField[]
     */
    public function getLeadFieldCustomFields(): array
    {
        $forceFilter = [
            [
                'column' => $this->getRepository()->getTableAlias().'.object',
                'expr'   => 'like',
                'value'  => 'lead',
            ],
            [
                'column' => $this->getRepository()->getTableAlias().'.dateAdded',
                'expr'   => 'isNotNull',
            ],
        ];

        return $this->getEntities([
            'filter' => [
                'force' => $forceFilter,
            ],
            'ignore_paginator' => true,
        ]);
    }

    /**
     * @return mixed[]
     */
    public function getLeadFieldCustomFieldSchemaDetails(): array
    {
        $fields  = $this->getLeadFieldCustomFields();
        $columns = $this->columnSchemaHelper->setName('leads')->getColumns();

        $schemaDetails = [];
        foreach ($fields as $value) {
            if (!empty($columns[$value->getAlias()])) {
                $schemaDetails[$value->getAlias()] = $columns[$value->getAlias()];
            }
        }

        return $schemaDetails;
    }

    /**
     * @return array
     */
    public function getCompanyFields()
    {
        return $this->getEntities([
            'filter' => [
                'force' => [
                    [
                        'column' => 'f.object',
                        'expr'   => 'like',
                        'value'  => 'company',
                    ],
                ],
            ],
        ]);
    }

    /**
     * @param LeadField $entity
     * @param bool      $unlock
     *
     * @throws AbortColumnCreateException
     * @throws AbortColumnUpdateException
     * @throws \Doctrine\DBAL\Exception
     * @throws DriverException
     * @throws SchemaException
     * @throws \Mautic\CoreBundle\Exception\SchemaException
     */
    public function saveEntity($entity, $unlock = true): void
    {
        if (!$entity instanceof LeadField) {
            throw new MethodNotAllowedHttpException(['LeadEntity']);
        }

        $this->setTimestamps($entity, $entity->isNew(), $unlock);

        if ('time' === $entity->getType()) {
            // time does not work well with list filters
            $entity->setIsListable(false);
        }

        if ($entity->isNew()) {
            try {
                $this->customFieldColumn->createLeadColumn($entity);
            } catch (CustomFieldLimitException $e) {
                // Convert to original Exception not to cause BC
                throw new \Doctrine\DBAL\Exception($this->translator->trans($e->getMessage()));
            }
        } else {
            $this->leadFieldSaver->saveLeadFieldEntity($entity, false);
            $this->customFieldColumn->updateLeadColumn($entity);
        }

        // Update order of the other fields.
        $this->reorderFieldsByEntity($entity);
    }

    /**
     * Build schema for each entity.
     *
     * @param array $entities
     * @param bool  $unlock
     *
     * @throws AbortColumnCreateException
     * @throws \Doctrine\DBAL\Exception
     * @throws DriverException
     * @throws SchemaException
     * @throws \Mautic\CoreBundle\Exception\SchemaException
     */
    public function saveEntities($entities, $unlock = true): void
    {
        foreach ($entities as $entity) {
            $this->saveEntity($entity, $unlock);
        }
    }

    /**
     * @param LeadField $entity
     *
     * @throws AbortColumnUpdateException
     * @throws \Doctrine\DBAL\Exception
     * @throws DriverException
     * @throws SchemaException
     */
    public function deleteEntity($entity): void
    {
        if (!$entity instanceof LeadField) {
            throw new MethodNotAllowedHttpException(['LeadEntity']);
        }
        $this->customFieldColumn->deleteLeadColumn($entity);
        $this->leadFieldDeleter->deleteLeadFieldEntity($entity);
    }

    /**
     * Delete an array of entities.
     *
     * @param mixed[] $ids
     *
     * @return mixed[]
     *
     * @throws \Mautic\CoreBundle\Exception\SchemaException
     */
    public function deleteEntities($ids): array
    {
        $entities = parent::deleteEntities($ids);

        /** @var LeadField $entity */
        foreach ($entities as $entity) {
            switch ($entity->getObject()) {
                case 'lead':
                    $this->columnSchemaHelper->setName('leads')->dropColumn($entity->getAlias())->executeChanges();
                    break;
                case 'company':
                    $this->columnSchemaHelper->setName('companies')->dropColumn($entity->getAlias())->executeChanges();
                    break;
            }
        }

        return $entities;
    }

    /**
     * Is field used in segment filter?
     */
    public function isUsedField(LeadField $field): bool
    {
        return $this->leadListModel->isFieldUsed($field);
    }

    /**
     * Returns list of all segments that use $field.
     *
     * @return Paginator
     */
    public function getFieldSegments(LeadField $field)
    {
        return $this->leadListModel->getFieldSegments($field);
    }

    /**
     * Filter used field ids.
     */
    public function filterUsedFieldIds(array $ids): array
    {
        return array_filter($ids, fn ($id): bool => false === $this->isUsedField($this->getEntity($id)));
    }

    /**
     * Reorder fields based on passed entity position.
     */
    public function reorderFieldsByEntity($entity): void
    {
        if (!$entity instanceof LeadField) {
            throw new MethodNotAllowedHttpException(['LeadEntity']);
        }

        $fields = $this->getRepository()->findBy([], ['order' => 'ASC']);
        $count  = 1;
        $order  = $entity->getOrder();
        $id     = $entity->getId();
        $hit    = false;
        foreach ($fields as $field) {
            if ($id !== $field->getId()) {
                if ($order === $field->getOrder()) {
                    if ($hit) {
                        $field->setOrder($count - 1);
                    } else {
                        $field->setOrder($count + 1);
                    }
                } else {
                    $field->setOrder($count);
                }
                $this->em->persist($field);
            } else {
                $hit = true;
            }
            ++$count;
        }
        $this->em->flush();
    }

    /**
     * Reorders fields by a list of field ids.
     *
     * @param int $start Number to start the order by (used for paginated reordering)
     */
    public function reorderFieldsByList(array $list, $start = 1): void
    {
        $fields = $this->getRepository()->findBy([], ['order' => 'ASC']);
        foreach ($fields as $field) {
            if (in_array($field->getId(), $list)) {
                $order = ((int) array_search($field->getId(), $list) + $start);
                $field->setOrder($order);
                $this->em->persist($field);
            }
        }
        $this->em->flush();
    }

    /**
     * Get list of custom field values for autopopulate fields.
     *
     * @param string $type
     * @param string $filter
     * @param int    $limit
     *
     * @return array
     */
    public function getLookupResults($type, $filter = '', $limit = 10)
    {
        /** @var LeadRepository $contactRepository */
        $contactRepository = $this->em->getRepository(Lead::class);

        return $contactRepository->getValueList($type, $filter, $limit);
    }

    /**
     * @param array $options
     *
     * @throws MethodNotAllowedHttpException
     */
    public function createForm($entity, FormFactoryInterface $formFactory, $action = null, $options = []): FormInterface
    {
        if (!$entity instanceof LeadField) {
            throw new MethodNotAllowedHttpException(['LeadField']);
        }

        if (!empty($action)) {
            $options['action'] = $action;
        }

        return $formFactory->create(FieldType::class, $entity, $options);
    }

    /**
     * @return string|true
     */
    public function setFieldProperties(LeadField $entity, array $properties)
    {
        if (!empty($properties) && is_array($properties)) {
            $properties = InputHelper::clean($properties);
        } else {
            $properties = [];
        }

        // validate properties
        $type   = $entity->getType();
        $result = FormFieldHelper::validateProperties($type, $properties);
        if ($result[0]) {
            $entity->setProperties($properties);

            return true;
        }

        return $result[1];
    }

    /**
     * @throws MethodNotAllowedHttpException
     */
    protected function dispatchEvent($action, &$entity, $isNew = false, ?Event $event = null): ?Event
    {
        switch ($action) {
            case 'pre_save':
                $action = LeadEvents::FIELD_PRE_SAVE;
                break;
            case 'post_save':
                $action = LeadEvents::FIELD_POST_SAVE;
                break;
            case 'pre_delete':
                $action = LeadEvents::FIELD_PRE_DELETE;
                break;
            case 'post_delete':
                $action = LeadEvents::FIELD_POST_DELETE;
                break;
        }

        if (!$entity instanceof LeadField) {
            throw new MethodNotAllowedHttpException(['LeadField']);
        }

        if (null !== $event && !$event instanceof LeadFieldEvent) {
            throw new \RuntimeException('Event should be LeadFieldEvent|null.');
        }

        try {
            return $this->fieldSaveDispatcher->dispatchEvent($action, $entity, $isNew, $event);
        } catch (NoListenerException) {
            return $event;
        }
    }

    /**
     * @deprecated Use FieldList::getFieldList method instead
     *
     * @param bool|true $byGroup
     * @param bool|true $alphabetical
     * @param array     $filters
     *
     * @return mixed[]
     */
    public function getFieldList($byGroup = true, $alphabetical = true, $filters = ['isPublished' => true, 'object' => 'lead']): array
    {
        return $this->fieldList->getFieldList($byGroup, $alphabetical, $filters);
    }

    /**
     * @param string $object
     *
     * @return array
     */
    public function getPublishedFieldArrays($object = 'lead')
    {
        return $this->getEntities(
            [
                'filter' => [
                    'force' => [
                        [
                            'column' => 'f.isPublished',
                            'expr'   => 'eq',
                            'value'  => true,
                        ],
                        [
                            'column' => 'f.object',
                            'expr'   => 'eq',
                            'value'  => $object,
                        ],
                    ],
                ],
                'hydration_mode' => 'HYDRATE_ARRAY',
                'result_cache'   => new ResultCacheOptions(LeadField::CACHE_NAMESPACE),
            ]
        );
    }

    /**
     * @param string $object
     */
    public function getFieldListWithProperties($object = 'lead'): array
    {
        return $this->getFieldsProperties(['object' => $object]);
    }

    /**
     * @param mixed[] $filters
     *
     * @return mixed[]
     */
    public function getFieldsProperties(array $filters = []): array
    {
        $forceFilters = [];

        foreach ($filters as $col => $val) {
            $forceFilters[] = [
                'column' => "f.{$col}",
                'expr'   => is_array($val) ? 'in' : 'eq',
                'value'  => $val,
            ];
        }

        $contactFields = $this->getEntities(
            [
                'filter' => [
                    'force' => $forceFilters,
                ],
                'ignore_paginator' => true,
                'hydration_mode'   => 'hydrate_array',
            ]
        );

        $fields = [];
        foreach ($contactFields as $contactField) {
            $fields[$contactField['alias']] = [
                'label'        => $contactField['label'],
                'alias'        => $contactField['alias'],
                'type'         => $contactField['type'],
                'group'        => $contactField['group'],
                'object'       => $contactField['object'],
                'group_label'  => $this->translator->trans('mautic.lead.field.group.'.$contactField['group']),
                'defaultValue' => $contactField['defaultValue'],
                'properties'   => $contactField['properties'],
                'isPublished'  => $contactField['isPublished'],
            ];
        }

        return $fields;
    }

    /**
     * Get the fields for a specific group.
     *
     * @param array $filters
     */
    public function getGroupFields($group, $filters = ['isPublished' => true]): array
    {
        $forceFilters = [
            [
                'column' => 'f.group',
                'expr'   => 'eq',
                'value'  => $group,
            ],
        ];
        foreach ($filters as $col => $val) {
            $forceFilters[] = [
                'column' => "f.{$col}",
                'expr'   => 'eq',
                'value'  => $val,
            ];
        }
        // Get a list of custom form fields
        $fields = $this->getEntities([
            'filter' => [
                'force' => $forceFilters,
            ],
            'orderBy'    => 'f.order',
            'orderByDir' => 'asc',
        ]);

        $leadFields = [];

        foreach ($fields as $f) {
            $leadFields[$f->getAlias()] = $f->getLabel();
        }

        return $leadFields;
    }

    public function getEntityByAlias($alias, $categoryAlias = null, $lang = null)
    {
        return $this->getRepository()->findOneByAlias($alias);
    }

    public function generateUniqueFieldAlias(string $alias): string
    {
        $originalAlias = $alias;
        $i             = 1;

        while ($this->getRepository()->findOneByAlias($alias)) {
            $alias = $originalAlias.'_'.$i;
            ++$i;
        }

        return $alias;
    }
}
