<?php

namespace Concrete\Attribute\Select;

use Concrete\Core\Api\ApiResourceValueInterface;
use Concrete\Core\Api\Attribute\OpenApiSpecifiableInterface;
use Concrete\Core\Api\Attribute\SupportsAttributeValueFromJsonInterface;
use Concrete\Core\Api\Fractal\Transformer\OptionListOptionTransformer;
use Concrete\Core\Api\OpenApi\SpecProperty;
use Concrete\Core\Api\Resources;
use Concrete\Core\Attribute\Component\OptionSelectInstanceFactory;
use Concrete\Core\Attribute\Controller as AttributeTypeController;
use Concrete\Core\Attribute\FontAwesomeIconFormatter;
use Concrete\Core\Attribute\SimpleTextExportableAttributeInterface;
use Concrete\Core\Entity\Attribute\Key\Key;
use Concrete\Core\Entity\Attribute\Key\Settings\SelectSettings;
use Concrete\Core\Entity\Attribute\Value\Value\SelectValue;
use Concrete\Core\Entity\Attribute\Value\Value\SelectValueOption;
use Concrete\Core\Entity\Attribute\Value\Value\SelectValueOptionList;
use Concrete\Core\Entity\Attribute\Value\Value\SelectValueUsedOption;
use Concrete\Core\Error\ErrorList\ErrorList;
use Concrete\Core\Error\UserMessageException;
use Concrete\Core\Search\ItemList\Database\AttributedItemList;
use Concrete\Core\Utility\Service\Xml;
use Core;
use Database;
use Doctrine\Common\Collections\ArrayCollection;
use League\Fractal\Resource\Collection;
use League\Fractal\Resource\ResourceAbstract;
use League\Fractal\Resource\ResourceInterface;
use Symfony\Component\HttpFoundation\JsonResponse;

class Controller extends AttributeTypeController implements
    SimpleTextExportableAttributeInterface,
    OpenApiSpecifiableInterface,
    SupportsAttributeValueFromJsonInterface,
    ApiResourceValueInterface
{
    protected $searchIndexFieldDefinition = [
        'type' => 'text',
        'options' => ['default' => null, 'notnull' => false],
    ];

    protected $akSelectAllowMultipleValues;

    protected $akSelectAllowOtherValues;

    protected $akHideNoneOption;

    protected $akSelectOptionDisplayOrder;

    protected $akDisplayMultipleValuesOnSelect;

    public function getIconFormatter()
    {
        return new FontAwesomeIconFormatter('list-alt');
    }

    public function getAttributeValueClass()
    {
        return SelectValue::class;
    }

    public function type_form()
    {
        $this->set('form', Core::make('helper/form'));
        $this->load();

        if ($this->request->getMethod() == 'POST') {
            $akSelectValues = $this->getSelectValuesFromPost();
            $this->set('akSelectValues', $akSelectValues);
        } elseif (isset($this->attributeKey)) {
            $options = $this->getOptions();
            $this->set('akSelectValues', $options);
        } else {
            $this->set('akSelectValues', []);
        }
    }

    public function exportKey($akey)
    {
        $this->load();

        $type = $akey->addChild('type');
        $type->addAttribute('allow-multiple-values', $this->akSelectAllowMultipleValues);
        $type->addAttribute('display-order', $this->akSelectOptionDisplayOrder);
        $type->addAttribute('display-multiple-values', $this->akDisplayMultipleValuesOnSelect);
        $type->addAttribute('allow-other-values', $this->akSelectAllowOtherValues);
        $type->addAttribute('hide-none-option', $this->akHideNoneOption);
        $options = $this->getOptions();
        $node = $type->addChild('options');
        foreach ($options as $option) {
            $opt = $node->addChild('option');
            $opt->addAttribute('value', $option->getSelectAttributeOptionValue());
            $opt->addAttribute('is-end-user-added', $option->isEndUserAdded());
        }

        return $akey;
    }

    public function exportValue(\SimpleXMLElement $akn)
    {
        $list = $this->getSelectedOptions();

        /** @var Xml $xmlService */
        $xmlService = $this->app->make(Xml::class);

        if ($list->count() > 0) {
            $av = $akn->addChild('value');

            foreach ($list as $l) {
                $xmlService->createChildElement($av, 'value', $l);
            }
        }
    }

    public function setAllowedMultipleValues($allow)
    {
        /**
         * @var SelectSettings
         */
        $type = $this->getAttributeKey()->getAttributeKeySettings();
        $type->setAllowMultipleValues($allow);
        $this->entityManager->persist($type);
        $this->entityManager->flush();
    }

    public function setAllowOtherValues($allow)
    {
        /**
         * @var SelectSettings
         */
        $type = $this->getAttributeKey()->getAttributeKeySettings();
        $type->setAllowOtherValues($allow);
        $this->entityManager->persist($type);
        $this->entityManager->flush();
    }

    public function setHideNoneOption($allow)
    {
        /**
         * @var SelectSettings
         */
        $type = $this->getAttributeKey()->getAttributeKeySettings();
        $type->setHideNoneOption($allow);
        $this->entityManager->persist($type);
        $this->entityManager->flush();
    }

    public function setOptionDisplayOrder($order)
    {
        /**
         * @var SelectSettings
         */
        $type = $this->getAttributeKey()->getAttributeKeySettings();
        $type->setDisplayOrder($order);
        $this->entityManager->persist($type);
        $this->entityManager->flush();
    }

    public function setOptions($options)
    {
        /**
         * @var SelectSettings
         */
        $type = $this->getAttributeKey()->getAttributeKeySettings();
        $list = new SelectValueOptionList();
        $list->setOptions($options);
        $type->setOptionList($list);
        $this->entityManager->persist($type);
        $this->entityManager->flush();
    }

    public function importKey(\SimpleXMLElement $akey)
    {
        /**
         * @var SelectSettings
         */
        $type = $this->getAttributeKeySettings();
        if (isset($akey->type)) {
            $xml = $this->app->make(Xml::class);
            $type->setAllowMultipleValues($xml->getBool($akey->type['allow-multiple-values']));
            $type->setDisplayOrder($akey->type['display-order']);
            $type->setAllowOtherValues($xml->getBool($akey->type['allow-other-values']));
            $type->setDisplayMultipleValuesOnSelect($xml->getBool($akey->type['display-multiple-values']));
            $type->setHideNoneOption($xml->getBool($akey->type['hide-none-option']));

            $list = new SelectValueOptionList();
            if (isset($akey->type->options)) {
                $displayOrder = 0;
                foreach ($akey->type->options->children() as $option) {
                    $opt = new SelectValueOption();
                    $opt->setSelectAttributeOptionValue((string) $option['value']);
                    $opt->setIsEndUserAdded($xml->getBool($option['is-end-user-added']));
                    $opt->setOptionList($list);
                    $opt->setDisplayOrder($displayOrder);
                    $list->getOptions()->add($opt);
                    $displayOrder++;
                }
            }
            $type->setOptionList($list);
        }

        return $type;
    }

    public function form()
    {
        $this->load();
        $selectedOptions = [];
        $selectedOptionIDs = [];
        if ($this->akSelectAllowOtherValues) {
            // This is the fancy auto complete, which uses an irritating way of handling
            // IDs so that we can discern whether something is an existing selected option
            // vs just a number that happens to match that option's ID.
            if ($_SERVER['REQUEST_METHOD'] == 'POST') {
                $options = $this->loadSelectedTagValueFromPost($this->request('atSelectOptionValue'));
                foreach ($options as $opt) {
                    $selectedOptions[] = ['id' => $opt->id, 'text' => $opt->text];
                    $selectedOptionIDs[] = $opt->id;
                }
            } else {
                $options = $this->getSelectedOptions();
                foreach ($options as $opt) {
                    $selectedOptions[] = ['id' => 'SelectAttributeOption:' . $opt->getSelectAttributeOptionID(), 'text' => $opt->getSelectAttributeOptionValue()];
                    $selectedOptionIDs[] = 'SelectAttributeOption:' . $opt->getSelectAttributeOptionID();
                }
            }
        } else {
            if ($_SERVER['REQUEST_METHOD'] == 'POST') {
                $options = $this->loadSelectedTagValueFromPost($this->request('atSelectOptionValue'));
                foreach ($options as $opt) {
                    $selectedOptionIDs[] = $opt->id;
                }
            } else {
                $options = $this->getSelectedOptions();
                foreach ($options as $opt) {
                    $selectedOptionIDs[] = $opt->getSelectAttributeOptionID();
                }
            }
        }
        $this->set('selectedOptionIDs', $selectedOptionIDs);
        $this->set('selectedOptions', $selectedOptions);
    }

    public function search()
    {
        $this->load();
        $selectedOptions = $this->request('atSelectOptionID');
        if (!is_array($selectedOptions)) {
            $selectedOptions = [];
        }
        $this->set('selectedOptions', $selectedOptions);
    }

    public function createAttributeValueFromRequest()
    {
        $data = $this->post();
        $this->load();

        $akSelectAllowMultipleValues = $this->akSelectAllowMultipleValues;
        $akSelectAllowOtherValues = $this->akSelectAllowOtherValues;
        $keyType = $this->attributeKey->getAttributeKeySettings();
        $optionList = $keyType->getOptionList();
        if (!$akSelectAllowMultipleValues && !$akSelectAllowOtherValues) {
            // select list. Only one option possible. No new options.
            if (isset($data['atSelectOptionValue'])) {
                $option = $this->getOptionByID($data['atSelectOptionValue']);
                if (is_object($option)) {
                    return $this->createAttributeValue($option);
                }
            }
            return $this->createAttributeValue(null);
        }
        if ($akSelectAllowMultipleValues && !$akSelectAllowOtherValues) {
            // checkbox list.  No new options.
            $options = [];
            if (isset($data['atSelectOptionValue']) && is_array($data['atSelectOptionValue'])) {
                foreach ($data['atSelectOptionValue'] as $optionID) {
                    $option = $this->getOptionByID($optionID);
                    if (is_object($option)) {
                        $options[] = $option;
                    }
                }
            }

            return $this->createAttributeValue($options);
        }
        if (!$akSelectAllowMultipleValues && $akSelectAllowOtherValues) {
            $option = false;
            if (isset($data['atSelectOptionValue'])) {
                if (preg_match(
                    '/SelectAttributeOption\:(.+)/i',
                    $data['atSelectOptionValue'],
                    $matches
                )) {
                    $option = $this->getOptionByID($matches[1]);
                } else {
                    $option = $this->getOptionByValue(trim($data['atSelectOptionValue']), $this->attributeKey);
                    if (!is_object($option)) {
                        $displayOrder = 0;
                        if ($optionList) {
                            $displayOrder = count($optionList->getOptions());
                        }
                        $option = new SelectValueOption();
                        $option->setOptionList($optionList);
                        $option->setIsEndUserAdded(true);
                        $option->setDisplayOrder($displayOrder);
                        $option->setSelectAttributeOptionValue(trim($data['atSelectOptionValue']));
                    }
                }
            }
            if (is_object($option)) {
                return $this->createAttributeValue($option);
            }

            return $this->createAttributeValue(null);
        }
        if ($akSelectAllowMultipleValues && $akSelectAllowOtherValues) {
            // The post comes through in the select2 format. A comma-separated
            // list of SelectAttributeOption:ID items and new items.
            $options = [];
            if (isset($data['atSelectOptionValue'])) {
                if (!is_array($data['atSelectOptionValue'])) {
                    $data['atSelectOptionValue'] = explode(',', $data['atSelectOptionValue']);
                }

                foreach ($data['atSelectOptionValue'] as $value) {
                    if (preg_match('/SelectAttributeOption\:(.+)/i', $value, $matches)) {
                        $option = $this->getOptionByID($matches[1]);
                    } else {
                        $option = $this->getOptionByValue(trim($value), $this->attributeKey);
                        if (!is_object($option)) {
                            $displayOrder = 0;
                            if ($optionList) {
                                $displayOrder = count($optionList->getOptions());
                            }
                            $option = new SelectValueOption();
                            $option->setOptionList($optionList);
                            $option->setDisplayOrder($displayOrder);
                            $option->setSelectAttributeOptionValue(trim($value));
                            $option->setIsEndUserAdded(true);
                        }
                    }

                    if (is_object($option)) {
                        $options[] = $option;
                    }
                }
            }

            if (count($options)) {
                return $this->createAttributeValue($options);
            }

            return $this->createAttributeValue(null);
        }
    }

    public function importValue(\SimpleXMLElement $akv)
    {
        if (isset($akv->value)) {
            $vals = [];
            foreach ($akv->value->children() as $ch) {
                $vals[] = (string) $ch;
            }

            return $this->createAttributeValue($vals);
        }
    }

    public function getOptionByID($id)
    {
        $orm = $this->entityManager;
        $repository = $orm->getRepository('\Concrete\Core\Entity\Attribute\Value\Value\SelectValueOption');

        return $repository->findOneBy([
            'avSelectOptionID' => $id,
        ]);
    }

    public function getOptionByValue($value, $attributeKey = false)
    {
        $orm = \Database::connection()->getEntityManager();
        $repository = $orm->getRepository('\Concrete\Core\Entity\Attribute\Value\Value\SelectValueOption');
        if ($attributeKey) {
            $existingList = $attributeKey->getAttributeKeySettings()->getOptionList();
        }
        if (isset($existingList) && is_object($existingList)) {
            $option = $repository->findOneBy([
                'list' => $existingList,
                'value' => $value,
            ]);
        } else {
            $option = $repository->findOneByValue($value);
        }

        return $option;
    }

    /**
     * Sets select options for a particular attribute
     * If the $value == string, then 1 item is selected
     * if array, then multiple, but only if the attribute in question is a select multiple
     * Note, items CANNOT be added to the pool (even if the attribute allows it) through this process.
     * Items should now be added to the database if they don't exist already & if the allow checkbox is
     * checked under the attribute settings
     * Code from this bug - http://www.concrete5.org/index.php?cID=595692.
     *
     * @param mixed $value
     */
    public function createAttributeValue($value)
    {
        $this->load();

        $options = [];
        $keyType = $this->attributeKey->getAttributeKeySettings();
        $optionList = $keyType->getOptionList();

        if ($value != null) {
            if (is_array($value) && $this->akSelectAllowMultipleValues) {
                foreach ($value as $v) {
                    if ($v instanceof SelectValueOption) {
                        $option = $v;
                    } else {
                        // Retrieve the option by value. Only get those that are assigned to this attribute key.
                        $option = $this->getOptionByValue($v, $this->attributeKey);
                    }

                    if (!is_object($option) && $this->akSelectAllowOtherValues) {
                        $option = new SelectValueOption();
                        $option->setIsEndUserAdded(true);
                        $option->setSelectAttributeOptionValue($v);
                        $option->setOptionList($optionList);
                    }

                    if (is_object($option)) {
                        $options[] = $option;
                    }
                }
            } else {
                if (is_array($value)) {
                    $value = $value[0];
                }

                if ($value instanceof SelectValueOption) {
                    $option = $value;
                } else {
                    $option = $this->getOptionByValue($value, $this->attributeKey);
                }
                if (is_object($option)) {
                    $option->setOptionList($optionList);
                    $options[] = $option;
                }
            }
        }

        $av = new SelectValue();
        $av->setSelectedOptions($options);

        return $av;
    }

    public function getDisplayValue()
    {
        $list = $this->getSelectedOptions();
        $html = '';
        foreach ($list as $l) {
            $html .= $l->getSelectAttributeOptionDisplayValue() . '<br/>';
        }

        return $html;
    }

    public function validateValue()
    {
        return is_object($value = $this->getAttributeValue()->getValue()) && ((string) $value != '');
    }

    public function validateKey($data = false)
    {
        $e = $this->app->make('error');
        $selectedPostValues = $this->getSelectValuesFromPost();
        if (empty($selectedPostValues)) {
            $e->add(t("You must at least add one value for this option list."));
        };

        return $e;

    }

    public function validateForm($p)
    {
        $this->load();
        $options = $this->request('atSelectOptionValue');

        return !empty($options);
    }

    public function searchForm($list)
    {
        $options = $this->request('atSelectOptionID');
        $db = $this->entityManager->getConnection();
        if (!is_array($options)) {
            return $list;
        }
        $optionQuery = [];
        foreach ($options as $id) {
            if ($id > 0) {
                $opt = $this->getOptionByID($id);
                if (is_object($opt)) {
                    $optionQuery[] = $opt->getSelectAttributeOptionValue(false);
                }
            }
        }
        if (count($optionQuery) == 0) {
            return false;
        }

        $i = 0;
        $multiString = '';
        foreach ($optionQuery as $val) {
            $val = $db->quote('%||' . $val . '||%');
            $multiString .= 'REPLACE(ak_' . $this->attributeKey->getAttributeKeyHandle() . ', "\n", "||") like ' . $val . ' ';
            if (($i + 1) < count($optionQuery)) {
                $multiString .= 'OR ';
            }
            $i++;
        }
        $list->filter(false, '(' . $multiString . ')');

        return $list;
    }

    public function getSearchIndexValue()
    {
        $str = "\n";
        if ($this->attributeValue && $this->attributeValue->getValue()) {
            $list = $this->attributeValue->getValue()->getSelectedOptions();
            foreach ($list as $l) {
                $str .= $l . "\n";
            }
        }
        // remove line break for empty list
        if ($str == "\n") {
            return '';
        }

        return $str;
    }

    /**
     * @return \Doctrine\Common\Collections\Collection|\Concrete\Core\Entity\Attribute\Value\Value\SelectValueOption[]
     */
    public function getSelectedOptions()
    {
        if ($this->attributeValue && ($value = $this->attributeValue->getValue())) {
            return $value->getSelectedOptions();
        }

        return [];
    }

    public function action_select_autocomplete_values()
    {
        $this->load();
        $componentInstanceFactory = $this->app->make(OptionSelectInstanceFactory::class);
        $componentInstance = $componentInstanceFactory->createInstance($this->attributeKey);
        if (!$componentInstanceFactory->instanceMatchesAccessToken(
            $componentInstance,
            $this->request->request->get('accessToken') ?? ''
        )) {
            throw new UserMessageException($this->app->make('token')->getErrorMessage());
        }

        $em = \Database::connection()->getEntityManager();
        $r = $em->getRepository('\Concrete\Core\Entity\Attribute\Value\Value\SelectValueOption');
        $type = $this->attributeKey->getAttributeKeySettings();
        $results = [];
        foreach ((array)$this->request->request->get('optionId') as $value) {
            if (strpos($value, 'SelectAttributeOption:') === 0) {
                $optionID = substr($value, 22);
                $option = $r->findOneBy(['list' => $type->getOptionList(), 'avSelectOptionID' => $optionID]);
                if ($option) {
                    $results[] = $componentInstance->createResultFromOption($option);
                }
            }
        }
        return new JsonResponse($results);
    }

    public function action_load_autocomplete_values()
    {
        $this->load();
        $componentInstanceFactory = $this->app->make(OptionSelectInstanceFactory::class);
        $componentInstance = $componentInstanceFactory->createInstance($this->attributeKey);
        if (!$componentInstanceFactory->instanceMatchesAccessToken($componentInstance, $this->request->request->get('accessToken') ?? '')) {
            throw new UserMessageException($this->app->make('token')->getErrorMessage());
        }



        $values = [];
        // now, if the current instance of the attribute key allows us to do autocomplete, we return all the values
        if ($this->akSelectAllowOtherValues) {
            $term = $this->request->request->get('query');
            $options = $this->getOptions($term);
            foreach ($options as $opt) {
                $values[] = $componentInstance->createResultFromOption($opt);
            }
        }

        return new JsonResponse($values);
    }

    public function getOptionUsageArray($parentPage = false, $limit = 9999)
    {
        $now = $this->app->make('date')->getOverridableNow();
        $db = Database::get();
        $q = <<<'EOT'
select
    atSelectOptions.value,
    atSelectOptions.avSelectOptionID,
    count(atSelectOptions.avSelectOptionID) as total
from
    Pages
    inner join CollectionVersions
        on Pages.cID = CollectionVersions.cID
        and CollectionVersions.cvIsApproved = 1
        and (CollectionVersions.cvPublishDate is null or CollectionVersions.cvPublishDate <= ?)
        and (CollectionVersions.cvPublishEndDate is null or CollectionVersions.cvPublishEndDate >= ?)
    inner join CollectionAttributeValues
        on CollectionVersions.cID = CollectionAttributeValues.cID and CollectionVersions.cvID = CollectionAttributeValues.cvID
    inner join atSelectOptionsSelected
        on (atSelectOptionsSelected.avID = CollectionAttributeValues.avID)
    inner join atSelectOptions
        on atSelectOptionsSelected.avSelectOptionID = atSelectOptions.avSelectOptionID
where
    Pages.cIsActive = 1
    and CollectionAttributeValues.akID = ?
EOT
        ;
        $v = [$now, $now, $this->attributeKey->getAttributeKeyID()];
        if (is_object($parentPage)) {
            $v[] = $parentPage->getCollectionID();
            $q .= 'and cParentID = ?';
        }
        $q .= ' group by avSelectOptionID order by total desc limit ' . $limit;
        $r = $db->Execute($q, $v);
        $options = new ArrayCollection();
        while ($row = $r->fetch()) {
            $opt = new SelectValueUsedOption();
            $opt->setSelectAttributeOptionValue($row['value']);
            $opt->setSelectAttributeOptionID($row['avSelectOptionID']);
            $opt->setSelectAttributeOptionUsageCount($row['total']);
            $options->add($opt);
        }

        return $options;
    }

    public function filterByAttribute(AttributedItemList $list, $value, $comparison = '=')
    {
        $em = \Database::connection()->getEntityManager();
        if ($value instanceof SelectValueOption) {
            $option = $value;
        } else {
            $option = $em->getRepository('\Concrete\Core\Entity\Attribute\Value\Value\SelectValueOption')
                ->findOneByValue($value)
            ;
        }

        $optionValue = is_object($option) ? $option->getSelectAttributeOptionValue(false) : $value;
        $column = 'ak_' . $this->attributeKey->getAttributeKeyHandle();
        $qb = $list->getQueryObject();
        $qb->andWhere(
            $comparison === '!=' || $comparison === '<>'
                ? $qb->expr()->notLike($column, ':optionValue_' . $this->attributeKey->getAttributeKeyID())
                : $qb->expr()->like($column, ':optionValue_' . $this->attributeKey->getAttributeKeyID())
        );
        $qb->setParameter('optionValue_' . $this->attributeKey->getAttributeKeyID(), "%\n" . $optionValue . "\n%");
    }

    /**
     * Returns a list of available options optionally filtered by an sql $like statement ex: startswith%.
     *
     * @param string $like
     * @param mixed|null $keywords
     */
    public function getOptions($keywords = null)
    {
        if (!isset($this->attributeKey)) {
            return [];
        }

        if (!is_object($this->attributeKey->getAttributeKeySettings())) {
            return [];
        }

        if (!isset($this->akSelectOptionDisplayOrder)) {
            $this->load();
        }

        $type = $this->attributeKey->getAttributeKeySettings();

        $em = \Database::get()->getEntityManager();
        $r = $em->getRepository('\Concrete\Core\Entity\Attribute\Value\Value\SelectValueOption');
        $builder = $r->createQueryBuilder('v');
        $builder->where('v.list = :list');
        $builder->andWhere('v.isDeleted = false');
        if ($keywords) {
            $builder->andWhere($builder->expr()->like('v.value', ':value'));
            $builder->setParameter('value', $keywords . '%');
        }
        $builder->setParameter('list', $type->getOptionList());
        $options = [];
        switch ($this->akSelectOptionDisplayOrder) {
            case 'popularity_desc':
                $builder->addSelect('count(va.generic_value) as total');
                $builder->leftJoin('v.values', 'va');
                $builder->groupBy('v.avSelectOptionID');
                $builder->orderBy('total', 'desc');
                $r = $builder->getQuery()->getResult();
                foreach ($r as $option) {
                    $options[] = $option[0];
                }
                break;
            case 'alpha_asc':
                $builder->orderBy('v.value', 'asc');
                $options = $builder->getQuery()->getResult();
                break;
            default:
                $builder->orderBy('v.displayOrder', 'asc');
                $options = $builder->getQuery()->getResult();
                break;
        }

        return $options;
    }

    public function saveKey($data)
    {
        /**
         * @var SelectSettings
         */
        $type = $this->getAttributeKeySettings();
        $optionList = $type->getOptionList();

        if (isset($data['akSelectAllowMultipleValues']) && ($data['akSelectAllowMultipleValues'] == 1)) {
            $akSelectAllowMultipleValues = 1;
        } else {
            $akSelectAllowMultipleValues = 0;
        }
        if (isset($data['akSelectAllowOtherValues']) && ($data['akSelectAllowOtherValues'] == 1)) {
            $akSelectAllowOtherValues = 1;
        } else {
            $akSelectAllowOtherValues = 0;
        }
        if (isset($data['akDisplayMultipleValuesOnSelect']) && ($data['akDisplayMultipleValuesOnSelect'] == 1)) {
            $akDisplayMultipleValuesOnSelect = 1;
        } else {
            $akDisplayMultipleValuesOnSelect = 0;
        }

        if (isset($data['akSelectOptionDisplayOrder']) && in_array(
                $data['akSelectOptionDisplayOrder'],
                ['display_asc', 'alpha_asc', 'popularity_desc']
            )
        ) {
            $akSelectOptionDisplayOrder = $data['akSelectOptionDisplayOrder'];
        } else {
            $akSelectOptionDisplayOrder = 'display_asc';
        }

        if (isset($data['akHideNoneOption']) && ($data['akHideNoneOption'] == 1)) {
            $akHideNoneOption = 1;
        } else {
            $akHideNoneOption = 0;
        }

        $type->setAllowMultipleValues((bool) $akSelectAllowMultipleValues);
        $type->setDisplayOrder($akSelectOptionDisplayOrder);
        $type->setAllowOtherValues((bool) $akSelectAllowOtherValues);
        $type->setDisplayMultipleValuesOnSelect((bool) $akDisplayMultipleValuesOnSelect);
        $type->setHideNoneOption((bool) $akHideNoneOption);

        $selectedPostValues = $this->getSelectValuesFromPost();

        // handle removing existing options that aren't in the post.
        foreach ($optionList->getOptions() as $option) {
            if (!in_array($option, $selectedPostValues)) {
                $option->setIsOptionDeleted(true);
            }
        }

        // handle adding the options.
        foreach ($selectedPostValues as $option) {
            if (!$option->getSelectAttributeOptionID()) { // this is a new option.
                $option->setOptionList($optionList);
                $optionList->getOptions()->add($option);
            }
        }

        $type->setOptionList($optionList);

        return $type;
    }

    /**
     * Convenience methods to retrieve a select attribute key's settings.
     */
    public function getAllowMultipleValues()
    {
        if ($this->akSelectAllowMultipleValues === null) {
            $this->load();
        }

        return $this->akSelectAllowMultipleValues;
    }

    public function getAllowOtherValues()
    {
        if ($this->akSelectAllowOtherValues === null) {
            $this->load();
        }

        return $this->akSelectAllowOtherValues;
    }

    public function getOptionDisplayOrder()
    {
        if ($this->akSelectOptionDisplayOrder === null) {
            $this->load();
        }

        return $this->akSelectOptionDisplayOrder;
    }

    public function getAttributeKeySettingsClass()
    {
        return SelectSettings::class;
    }

    public function getControlID()
    {
        return $this->field('atSelectOptionValue');
    }

    /**
     * {@inheritdoc}
     *
     * @see \Concrete\Core\Attribute\SimpleTextExportableAttributeInterface::getAttributeValueTextRepresentation()
     */
    public function getAttributeValueTextRepresentation()
    {
        $value = $this->getAttributeValueObject();
        if ($value === null) {
            $result = '';
        } else {
            $options = [];
            foreach ($value->getSelectedOptions() as $selectedOption) {
                /** @var SelectValueOption $selectedOption */
                $options[] = $selectedOption->getSelectAttributeOptionValue();
            }
            $result = implode("\n", $options);
        }

        return $result;
    }

    /**
     * {@inheritdoc}
     *
     * @see \Concrete\Core\Attribute\SimpleTextExportableAttributeInterface::updateAttributeValueFromTextRepresentation()
     */
    public function updateAttributeValueFromTextRepresentation($textRepresentation, ErrorList $warnings)
    {
        $value = $this->getAttributeValueObject();
        $optionTexts = explode("\n", $textRepresentation);
        $optionTexts = array_filter(
            array_map('trim', $optionTexts),
            function ($optionText) {
                return $optionText !== '';
            }
        );
        $numOptions = count($optionTexts);
        if ($numOptions === 0) {
            if ($value !== null) {
                $value->getSelectedOptions()->clear();
            }
        } else {
            $settings = $this->getAttributeKeySettings();
            /** @var SelectSettings $settings */
            if ($numOptions > 1 && !$settings->getAllowMultipleValues()) {
                $warnings->add(t('The Select Attribute with handle "%s" does not support multiple options: only the first one will be imported', $this->attributeKey->getAttributeKeyHandle()));
                $firstOptionText = array_shift($optionTexts);
                $optionTexts = [$firstOptionText];
                $numOptions = 1;
            }
            $optionList = $settings->getOptionList();
            $firstOption = true;
            foreach ($optionTexts as $optionText) {
                $selectedOption = null;
                $deletedOptions = [];
                foreach ($optionList->getOptions() as $option) {
                    if ($option->isOptionDeleted()) {
                        $deletedOptions[$option->getSelectAttributeOptionValue()] = $option;
                    } elseif ($option->getSelectAttributeOptionValue() === $optionText) {
                        $selectedOption = $option;
                        break;
                    }
                }
                if ($selectedOption === null) {
                    if ($settings->getAllowOtherValues()) {
                        $displayOrder = 0;
                        if ($optionList) {
                            $displayOrder = count($optionList->getOptions());
                        }
                        $selectedOption = new SelectValueOption();
                        $selectedOption->setOptionList($optionList);
                        $selectedOption->setIsEndUserAdded(true);
                        $selectedOption->setDisplayOrder($displayOrder);
                        $selectedOption->setSelectAttributeOptionValue($optionText);
                        $optionList->getOptions()->add($selectedOption);
                    } elseif (isset($deletedOptions[$optionText])) {
                        $selectedOption = $deletedOptions[$optionText];
                        $warnings->add(t('"%1$s" matches a deleted option the Select Attribute with handle "%2$s"', $optionText, $this->attributeKey->getAttributeKeyHandle()));
                    } else {
                        $warnings->add(t('"%1$s" is not a valid option for the Select Attribute with handle "%2$s"', $optionText, $this->attributeKey->getAttributeKeyHandle()));
                    }
                }
                if ($selectedOption !== null) {
                    if ($value === null) {
                        $value = new SelectValue();
                    }
                    if ($firstOption) {
                        $firstOption = false;
                        $value->getSelectedOptions()->clear();
                    }
                    $value->getSelectedOptions()->add($selectedOption);
                }
            }
        }

        return $value;
    }

    protected function load()
    {
        /** @var \Concrete\Core\Entity\Attribute\Key\SelectKey */
        $ak = $this->getAttributeKey();
        if (!is_object($ak)) {
            return false;
        }

        /**
         * @var SelectSettings
         */
        $type = $ak->getAttributeKeySettings();
        if (is_object($type)) {
            $this->akSelectAllowMultipleValues = $type->getAllowMultipleValues();
            $this->akSelectAllowOtherValues = $type->getAllowOtherValues();
            $this->akSelectOptionDisplayOrder = $type->getDisplayOrder();
            $this->akDisplayMultipleValuesOnSelect = $type->getDisplayMultipleValuesOnSelect();
            $this->akHideNoneOption = $type->getHideNoneOption();

            $this->set('akDisplayMultipleValuesOnSelect', $this->akDisplayMultipleValuesOnSelect);
            $this->set('akSelectAllowMultipleValues', $this->akSelectAllowMultipleValues);
            $this->set('akSelectAllowOtherValues', $this->akSelectAllowOtherValues);
            $this->set('akSelectOptionDisplayOrder', $this->akSelectOptionDisplayOrder);
            $this->set('akHideNoneOption', $this->akHideNoneOption);
        }
    }

    /**
     * Used by selectize. Automatically takes a value request and converts it into tag/text key value pairs.
     * New options are just text/tag, whereas existing ones are SelectAttributeOption:ID/text.
     *
     * @param mixed $value
     */
    protected function loadSelectedTagValueFromPost($value)
    {
        $em = \Database::get()->getEntityManager();
        $r = $em->getRepository('\Concrete\Core\Entity\Attribute\Value\Value\SelectValueOption');
        $type = $this->attributeKey->getAttributeKeySettings();

        if (!is_array($value)) {
            $values = explode(',', $value);
        } else {
            $values = $value;
        }

        $response = [];
        foreach ($values as $value) {
            $value = trim($value);
            $o = new \stdClass();
            if (strpos($value, 'SelectAttributeOption:') === 0) {
                $optionID = substr($value, 22);
                $option = $r->findOneBy(['list' => $type->getOptionList(), 'avSelectOptionID' => $optionID]);
                if (is_object($option)) {
                    $o->id = $value;
                    $o->text = $option->getSelectAttributeOptionValue();
                }
            } else {
                $o->id = $value;
                $o->text = $value;
            }

            $response[] = $o;
        }

        return $response;
    }

    private function getSelectValuesFromPost()
    {
        $displayOrder = 0;
        $options = [];
        foreach ($_POST as $key => $value) {
            if (!strstr($key, 'akSelectValue_') || $value == 'TEMPLATE') {
                continue;
            }
            $opt = false;
            // strip off the prefix to get the ID
            $id = substr($key, 14);
            // now we determine from the post whether this is a new option
            // or an existing. New ones have this value from in the akSelectValueNewOption_ post field
            if (isset($_POST['akSelectValueNewOption_' . $id]) && $_POST['akSelectValueNewOption_' . $id] == $id) {
                $opt = new SelectValueOption();
                $opt->setSelectAttributeOptionValue($value);
                $opt->setDisplayOrder($displayOrder);
            } elseif (isset($_POST['akSelectValueExistingOption_' . $id]) && $_POST['akSelectValueExistingOption_' . $id] == $id) {
                $opt = $this->getOptionByID($id);
                $opt->setSelectAttributeOptionValue($value);
                $opt->setDisplayOrder($displayOrder);
            }

            if (is_object($opt)) {
                $options[] = $opt;
                $displayOrder++;
            }
        }

        return $options;
    }

    public function getOpenApiSpecProperty(Key $key): SpecProperty
    {
        return new SpecProperty(
            $key->getAttributeKeyHandle(),
            $key->getAttributeKeyDisplayName(),
            'array',
            null,
            ['type' => 'integer'],
        );
    }

    public function createAttributeValueFromNormalizedJson($json)
    {
        $type = $this->attributeKey->getAttributeKeySettings();
        $r = $this->entityManager->getRepository(SelectValueOption::class);
        $options = [];
        if (is_array($json)) {
            foreach ($json as $optionID) {
                $option = $r->findOneBy(['list' => $type->getOptionList(), 'avSelectOptionID' => $optionID]);
                if ($option) {
                    $options[] = $option;
                }
            }
        }
        $av = new SelectValue();
        $av->setSelectedOptions($options);
        return $av;
    }

    public function getApiValueResource(): ?ResourceInterface
    {
        $options = $this->getSelectedOptions();
        return new Collection($options, new OptionListOptionTransformer(), Resources::RESOURCE_OPTION_LIST_OPTIONS);
    }




}
