<?php
/**
 * Copyright 2015 Adobe
 * All Rights Reserved.
 */
namespace Magento\DownloadableImportExport\Model\Import\Product\Type;

use Magento\CatalogImportExport\Model\Import\Product as ImportProduct;
use Magento\Downloadable\Model\Url\DomainValidator;
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\CollectionFactory as AttributeOptionCollectionFactory;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Store\Model\Store;

/**
 * phpcs:disable Magento2.Commenting.ConstantsPHPDocFormatting
 * @SuppressWarnings(PHPMD.TooManyFields)
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
 */
class Downloadable extends \Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType
{
    /**
     * Pair value separator.
     */
    public const PAIR_VALUE_SEPARATOR = '=';

    /**
     * Default sort order
     */
    public const DEFAULT_SORT_ORDER = 0;

    /**
     * Default number of downloads
     */
    public const DEFAULT_NUMBER_OF_DOWNLOADS = 0;

    /**
     * Default is shareable
     */
    public const DEFAULT_IS_SHAREABLE = 2;

    /**
     * Default website id
     */
    public const DEFAULT_WEBSITE_ID = 0;

    /**
     * Patch for downloadable files samples
     */
    public const DOWNLOADABLE_PATCH_SAMPLES = 'downloadable/files/samples';

    /**
     * Patch for downloadable files links
     */
    public const DOWNLOADABLE_PATCH_LINKS = 'downloadable/files/links';

    /**
     * Patch for downloadable files link samples
     */
    public const DOWNLOADABLE_PATCH_LINK_SAMPLES = 'downloadable/files/link_samples';

    /**
     * Type option for url
     */
    public const URL_OPTION_VALUE = 'url';

    /**
     * Type option for file
     */
    public const FILE_OPTION_VALUE = 'file';

    /**
     * Column with downloadable samples
     */
    public const COL_DOWNLOADABLE_SAMPLES = 'downloadable_samples';

    /**
     * Column with downloadable links
     */
    public const COL_DOWNLOADABLE_LINKS = 'downloadable_links';

    /**
     * Default group title
     */
    public const DEFAULT_GROUP_TITLE = '';

    /**
     * Default links can be purchased separately
     */
    public const DEFAULT_PURCHASED_SEPARATELY = 1;

    /**
     * Error codes.
     */
    public const ERROR_OPTIONS_NOT_FOUND = 'optionsNotFound';

    public const ERROR_GROUP_TITLE_NOT_FOUND = 'groupTitleNotFound';

    public const ERROR_OPTION_NO_TITLE = 'optionNoTitle';

    public const ERROR_MOVE_FILE = 'moveFile';

    public const ERROR_COLS_IS_EMPTY = 'emptyOptions';

    private const ERROR_LINK_URL_NOT_IN_DOMAIN_WHITELIST = 'linkUrlNotInDomainWhitelist';

    private const ERROR_SAMPLE_URL_NOT_IN_DOMAIN_WHITELIST = 'sampleUrlNotInDomainWhitelist';

    /**
     * Validation failure message template definitions
     *
     * @var array
     */
    protected $_messageTemplates = [
        self::ERROR_OPTIONS_NOT_FOUND => 'Options for downloadable products not found',
        self::ERROR_GROUP_TITLE_NOT_FOUND => 'Group titles not found for downloadable products',
        self::ERROR_OPTION_NO_TITLE => 'Option no title',
        self::ERROR_MOVE_FILE => 'Error move file',
        self::ERROR_COLS_IS_EMPTY => 'Missing sample and links data for the downloadable product',
        self::ERROR_LINK_URL_NOT_IN_DOMAIN_WHITELIST =>
            'Link URL\'s domain is not in list of downloadable_domains in env.php.',
        self::ERROR_SAMPLE_URL_NOT_IN_DOMAIN_WHITELIST =>
            'Sample URL\'s domain is not in list of downloadable_domains in env.php.'
    ];

    /**
     * Entity model parameters.
     *
     * @var array
     */
    protected $parameters = [];

    /**
     * Ids products
     *
     * @var array
     */
    protected $productIds = [];

    /**
     * Array of cached options.
     *
     * @var array
     */
    protected $cachedOptions = [
        'link' => [],
        'sample' => []
    ];

    /**
     * Instance of empty sample
     *
     * @var array
     */
    protected $dataSample = [
        'sample_id' => null,
        'product_id' => null,
        'sample_url' => null,
        'sample_file' => null,
        'sample_type' => null,
        'sort_order' => self::DEFAULT_SORT_ORDER
    ];

    /**
     * Instance of empty sample title
     *
     * @var array
     */
    protected $dataSampleTitle = [
        'sample_id' => null,
        'store_id' => Store::DEFAULT_STORE_ID,
        'title' => null
    ];

    /**
     * Instance of empty link
     *
     * @var array
     */
    protected $dataLink = [
        'link_id' => null,
        'product_id' => null,
        'sort_order' => self::DEFAULT_SORT_ORDER,
        'number_of_downloads' => self::DEFAULT_NUMBER_OF_DOWNLOADS,
        'is_shareable' => self::DEFAULT_IS_SHAREABLE,
        'link_url' => null,
        'link_file' => null,
        'link_type' => null,
        'sample_url' => null,
        'sample_file' => null,
        'sample_type' => null
    ];

    /**
     * Instance of empty link title
     *
     * @var array
     */
    protected $dataLinkTitle = [
        'link_id' => null,
        'store_id' => Store::DEFAULT_STORE_ID,
        'title' => null
    ];

    /**
     * Instance of empty link price
     *
     * @var array
     */
    protected $dataLinkPrice = [
        'price_id' => null,
        'link_id' => null,
        'website_id' => self::DEFAULT_WEBSITE_ID,
        'price' => null
    ];

    /**
     * @var array
     */
    protected $optionLinkMapping = [
        'sortorder' => 'sort_order',
        'downloads' => 'number_of_downloads',
        'shareable' => 'is_shareable',
        'url' => 'link_url',
        'file' => 'link_file',
    ];

    /**
     * @var array
     */
    protected $optionSampleMapping = [
        'sortorder' => 'sort_order',
        'url' => 'sample_url',
        'file' => 'sample_file',
    ];

    /**
     * Num row parsing file
     *
     * @var int
     */
    protected $rowNum;

    /**
     * @var \Magento\DownloadableImportExport\Helper\Uploader
     */
    protected $uploaderHelper;

    /**
     * @var \Magento\DownloadableImportExport\Helper\Data
     */
    protected $downloadableHelper;

    /**
     * @var DomainValidator
     */
    private $domainValidator;

    /**
     * Downloadable constructor
     *
     * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory $attrSetColFac
     * @param \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory $prodAttrColFac
     * @param \Magento\Framework\App\ResourceConnection $resource
     * @param array $params
     * @param \Magento\DownloadableImportExport\Helper\Uploader $uploaderHelper
     * @param \Magento\DownloadableImportExport\Helper\Data $downloadableHelper
     * @param DomainValidator $domainValidator
     * @param MetadataPool|null $metadataPool
     * @param AttributeOptionCollectionFactory|null $attributeOptionCollectionFactory
     */
    public function __construct(
        \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory $attrSetColFac,
        \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory $prodAttrColFac,
        \Magento\Framework\App\ResourceConnection $resource,
        array $params,
        \Magento\DownloadableImportExport\Helper\Uploader $uploaderHelper,
        \Magento\DownloadableImportExport\Helper\Data $downloadableHelper,
        DomainValidator $domainValidator,
        ?MetadataPool $metadataPool = null,
        ?AttributeOptionCollectionFactory $attributeOptionCollectionFactory = null
    ) {
        parent::__construct(
            $attrSetColFac,
            $prodAttrColFac,
            $resource,
            $params,
            $metadataPool,
            $attributeOptionCollectionFactory
        );
        $this->parameters = $this->_entityModel->getParameters();
        $this->_resource = $resource;
        $this->uploaderHelper = $uploaderHelper;
        $this->domainValidator = $domainValidator;
        $this->downloadableHelper = $downloadableHelper;
    }

    /**
     * Save product type specific data.
     *
     * @return \Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType
     */
    public function saveData()
    {
        $newSku = $this->_entityModel->getNewSku();
        while ($bunch = $this->_entityModel->getNextBunch()) {
            foreach ($bunch as $rowNum => $rowData) {
                if (!$this->_entityModel->isRowAllowedToImport($rowData, $rowNum)) {
                    continue;
                }
                $rowSku = strtolower($rowData[ImportProduct::COL_SKU] ?? '');
                $productData = $newSku[$rowSku];
                if ($this->_type != $productData['type_id']) {
                    continue;
                }
                $this->parseOptions($rowData, $productData[$this->getProductEntityLinkField()]);
            }
            if (!empty($this->cachedOptions['sample']) || !empty($this->cachedOptions['link'])) {
                $this->saveOptions();
                $this->clear();
            }
        }
        return $this;
    }

    /**
     * Validate row attributes. Pass VALID row data ONLY as argument.
     *
     * @param array $rowData
     * @param int $rowNum
     * @param bool $isNewProduct Optional
     *
     * @return bool
     *
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function isRowValid(array $rowData, $rowNum, $isNewProduct = true)
    {
        $this->rowNum = $rowNum;
        $error = false;
        if (!$this->downloadableHelper->isRowDownloadableNoValid($rowData) && $isNewProduct) {
            $this->_entityModel->addRowError(self::ERROR_OPTIONS_NOT_FOUND, $this->rowNum);
            $error = true;
        }
        if ($this->downloadableHelper->isRowDownloadableEmptyOptions($rowData)) {
            $this->_entityModel->addRowError(self::ERROR_COLS_IS_EMPTY, $this->rowNum);
            $error = true;
        }
        if ($this->isRowValidSample($rowData) || $this->isRowValidLink($rowData)) {
            $error = true;
        }
        return !$error;
    }

    /**
     * Validation sample options
     *
     * @param array $rowData
     * @return bool
     */
    protected function isRowValidSample(array $rowData): bool
    {
        $hasSampleLinkData = (
            isset($rowData[self::COL_DOWNLOADABLE_SAMPLES]) &&
            (
                $rowData[self::COL_DOWNLOADABLE_SAMPLES] != '' ||
                !empty($rowData[self::COL_DOWNLOADABLE_SAMPLES])
            )
        );

        if (!$hasSampleLinkData) {
            return false;
        }

        if (is_array($rowData[self::COL_DOWNLOADABLE_SAMPLES])) {
            $sampleData = $this->prepareStructuredSampleData($rowData[self::COL_DOWNLOADABLE_SAMPLES]);
        } else {
            $sampleData = $this->prepareSampleData($rowData[self::COL_DOWNLOADABLE_SAMPLES]);
        }

        return $this->validateData($sampleData);
    }

    /**
     * Validation links option
     *
     * @param array $rowData
     * @return bool
     */
    protected function isRowValidLink(array $rowData): bool
    {
        $hasLinkData = (
            isset($rowData[self::COL_DOWNLOADABLE_LINKS]) &&
            (
                $rowData[self::COL_DOWNLOADABLE_LINKS] != '' ||
                !empty($rowData[self::COL_DOWNLOADABLE_LINKS])
            )
        );

        if (!$hasLinkData) {
            return false;
        }

        if (is_array($rowData[self::COL_DOWNLOADABLE_LINKS])) {
            $linkData = $this->prepareStructuredLinkData($rowData[self::COL_DOWNLOADABLE_LINKS]);
        } else {
            $linkData = $this->prepareLinkData($rowData[self::COL_DOWNLOADABLE_LINKS]);
        }

        return $this->validateData($linkData);
    }

    /**
     * Validate samples and links urls.
     *
     * @param array $data
     * @return bool
     */
    private function validateData(array $data): bool
    {
        $result = $this->isTitle($data);

        foreach ($data as $item) {
            if ($this->hasDomainNotInWhitelist($item, 'link_type', 'link_url')) {
                $this->_entityModel->addRowError(self::ERROR_LINK_URL_NOT_IN_DOMAIN_WHITELIST, $this->rowNum);
                $result = true;
            }

            if ($this->hasDomainNotInWhitelist($item, 'sample_type', 'sample_url')) {
                $this->_entityModel->addRowError(self::ERROR_SAMPLE_URL_NOT_IN_DOMAIN_WHITELIST, $this->rowNum);
                $result = true;
            }
        }

        return $result;
    }

    /**
     * Check isset title for all options
     *
     * @param array $options
     * @return bool
     */
    protected function isTitle(array $options)
    {
        $result = false;
        foreach ($options as $option) {
            if (!array_key_exists('title', $option)) {
                $this->_entityModel->addRowError(self::ERROR_OPTION_NO_TITLE, $this->rowNum);
                $result = true;
            }
        }
        return $result;
    }

    /**
     * Prepare attributes with default value for save.
     *
     * @param array $rowData
     * @param bool $withDefaultValue
     * @return array
     */
    public function prepareAttributesWithDefaultValueForSave(array $rowData, $withDefaultValue = true)
    {
        $resultAttrs = parent::prepareAttributesWithDefaultValueForSave($rowData, $withDefaultValue);
        $resultAttrs = array_merge($resultAttrs, $this->addAdditionalAttributes($rowData));
        return $resultAttrs;
    }

    /**
     * Get additional attributes for downloadable product.
     *
     * @param array $rowData
     * @return array
     */
    protected function addAdditionalAttributes(array $rowData)
    {
        return [
            'samples_title' => $this->sampleGroupTitle($rowData),
            'links_title' => $this->linksAdditionalAttributes($rowData, 'group_title', self::DEFAULT_GROUP_TITLE),
            'links_purchased_separately' => $this->linksAdditionalAttributes(
                $rowData,
                'purchased_separately',
                self::DEFAULT_PURCHASED_SEPARATELY
            )
        ];
    }

    /**
     * Get additional attributes for links
     *
     * @param array $rowData
     * @param string $attribute
     * @param mixed $defaultValue
     * @return string
     */
    protected function linksAdditionalAttributes(array $rowData, $attribute, $defaultValue)
    {
        $result = $defaultValue;
        if (isset($rowData[self::COL_DOWNLOADABLE_LINKS])) {
            $links = $rowData[self::COL_DOWNLOADABLE_LINKS];

            if (is_array($links)) {
                $options = $links;
            } else {
                $options = explode(ImportProduct::PSEUDO_MULTI_LINE_SEPARATOR, $links);
            }

            foreach ($options as $option) {
                if (is_array($option)) {
                    $arr = $this->parseStructuredLinkOption($option);
                } else {
                    $arr = $this->parseLinkOption(explode($this->_entityModel->getMultipleValueSeparator(), $option));
                }

                if (isset($arr[$attribute])) {
                    $result = $arr[$attribute];
                    break;
                }
            }
        }

        return $result;
    }

    /**
     * Get group title for sample
     *
     * @param array $rowData
     * @return string
     */
    protected function sampleGroupTitle(array $rowData)
    {
        $result = '';
        if (isset($rowData[self::COL_DOWNLOADABLE_SAMPLES])) {
            $samples = $rowData[self::COL_DOWNLOADABLE_SAMPLES];

            if (is_array($samples)) {
                $options = $samples;
            } else {
                $options = explode(ImportProduct::PSEUDO_MULTI_LINE_SEPARATOR, $samples);
            }
            foreach ($options as $option) {
                if (is_array($option)) {
                    $arr = $this->parseStructuredSampleOption($option);
                } else {
                    $arr = $this->parseSampleOption(explode($this->_entityModel->getMultipleValueSeparator(), $option));
                }

                if (isset($arr['group_title'])) {
                    $result = $arr['group_title'];
                    break;
                }
            }
        }
        return $result;
    }

    /**
     * Parse options for products
     *
     * @param array $rowData
     * @param int $entityId
     * @return $this
     */
    protected function parseOptions(array $rowData, $entityId)
    {
        $this->productIds[] = $entityId;
        if (isset($rowData[self::COL_DOWNLOADABLE_LINKS])) {
            if (is_array($rowData[self::COL_DOWNLOADABLE_LINKS])) {
                $this->cachedOptions['link'] = array_merge(
                    $this->cachedOptions['link'],
                    $this->prepareStructuredLinkData($rowData[self::COL_DOWNLOADABLE_LINKS], $entityId)
                );
            } else {
                $this->cachedOptions['link'] = array_merge(
                    $this->cachedOptions['link'],
                    $this->prepareLinkData($rowData[self::COL_DOWNLOADABLE_LINKS], $entityId)
                );
            }
        }
        if (isset($rowData[self::COL_DOWNLOADABLE_SAMPLES])) {
            if (is_array($rowData[self::COL_DOWNLOADABLE_SAMPLES])) {
                $this->cachedOptions['sample'] = array_merge(
                    $this->prepareStructuredSampleData($rowData[self::COL_DOWNLOADABLE_SAMPLES], $entityId),
                    $this->cachedOptions['sample']
                );
            } else {
                $this->cachedOptions['sample'] = array_merge(
                    $this->prepareSampleData($rowData[self::COL_DOWNLOADABLE_SAMPLES], $entityId),
                    $this->cachedOptions['sample']
                );
            }
        }
        return $this;
    }

    /**
     * Get fill data options with key sample
     *
     * @param array $base
     * @param array $options
     * @return array
     */
    protected function fillDataSample(array $base, array $options)
    {
        $result = [];
        $existingOptions = $this->connection->fetchAll(
            $this->connection->select()->from(
                $this->_resource->getTableName('downloadable_sample')
            )->where(
                'product_id in (?)',
                $this->productIds
            )
        );
        foreach ($options as $option) {
            $isExist = false;
            foreach ($existingOptions as $existingOption) {
                if ($option['sample_url'] == $existingOption['sample_url']
                    && $option['sample_file'] == $existingOption['sample_file']
                    && $option['sample_type'] == $existingOption['sample_type']
                    && $option['product_id'] == $existingOption['product_id']) {
                    $result[] = array_replace($this->dataSampleTitle, $option, $existingOption);
                    $isExist = true;
                }
            }
            if (!$isExist) {
                $result[] = array_replace($base, $option);
            }
        }
        return $result;
    }

    /**
     * Get fill data options with key link
     *
     * @param array $base
     * @param array $options
     * @return array
     */
    protected function fillDataLink(array $base, array $options)
    {
        $result = [];
        $existingOptions = $this->connection->fetchAll(
            $this->connection->select()->from(
                $this->_resource->getTableName('downloadable_link')
            )->where(
                'product_id in (?)',
                $this->productIds
            )
        );
        foreach ($options as $option) {
            $existOption = $this->downloadableHelper->fillExistOptions($base, $option, $existingOptions);
            if (empty($existOption)) {
                $result[] = array_replace($base, $option);
            } else {
                $result[] = $existOption;
            }
        }
        return $result;
    }

    /**
     * Get fill data options with key link
     *
     * @param array $options
     * @return array
     */
    protected function fillDataTitleLink(array $options)
    {
        $result = [];
        $existingOptions = $this->connection->fetchAll(
            $this->connection->select()->from(
                ['dl' => $this->_resource->getTableName('downloadable_link')],
                [
                    'link_id',
                    'product_id',
                    'sort_order',
                    'number_of_downloads',
                    'is_shareable',
                    'link_url',
                    'link_file',
                    'link_type',
                    'sample_url',
                    'sample_file',
                    'sample_type'
                ]
            )->joinLeft(
                ['dlp' => $this->_resource->getTableName('downloadable_link_price')],
                'dl.link_id = dlp.link_id AND dlp.website_id=' . self::DEFAULT_WEBSITE_ID,
                ['price_id', 'website_id']
            )->where(
                'product_id in (?)',
                $this->productIds
            )
        );
        foreach ($options as $option) {
            $existOption = $this->downloadableHelper->fillExistOptions($this->dataLinkTitle, $option, $existingOptions);
            if (!empty($existOption)) {
                $result['title'][] = $existOption;
            }
            $existOption = $this->downloadableHelper->fillExistOptions($this->dataLinkPrice, $option, $existingOptions);
            if (!empty($existOption)) {
                $result['price'][] = $existOption;
            }
        }
        return $result;
    }

    /**
     * Upload all sample files
     *
     * @param array $options
     * @return array
     */
    protected function uploadSampleFiles(array $options)
    {
        $result = [];
        foreach ($options as $option) {
            if ($option['sample_file'] !== null) {
                $option['sample_file'] = $this->uploadDownloadableFiles($option['sample_file'], 'samples', true);
            }
            $result[] = $option;
        }
        return $result;
    }

    /**
     * Upload all link files
     *
     * @param array $options
     * @return array
     */
    protected function uploadLinkFiles(array $options)
    {
        $result = [];
        foreach ($options as $option) {
            if ($option['sample_file'] !== null) {
                $option['sample_file'] = $this->uploadDownloadableFiles($option['sample_file'], 'link_samples', true);
            }
            if ($option['link_file'] !== null) {
                $option['link_file'] = $this->uploadDownloadableFiles($option['link_file'], 'links', true);
            }
            $result[] = $option;
        }
        return $result;
    }

    /**
     * Save options in base
     *
     * @return \Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType
     */
    protected function saveOptions()
    {
        $options = $this->cachedOptions;
        if (!empty($options['sample'])) {
            $this->saveSampleOptions();
        }
        if (!empty($options['link'])) {
            $this->saveLinkOptions();
        }
        return $this;
    }

    /**
     * Save sample options
     *
     * @return $this
     */
    protected function saveSampleOptions()
    {
        $options['sample'] = $this->uploadSampleFiles($this->cachedOptions['sample']);
        $dataSample = $this->fillDataSample($this->dataSample, $options['sample']);
        $this->connection->insertOnDuplicate(
            $this->_resource->getTableName('downloadable_sample'),
            $this->downloadableHelper->prepareDataForSave($this->dataSample, $dataSample)
        );
        $titleSample = $this->fillDataSample($this->dataSampleTitle, $options['sample']);
        $this->connection->insertOnDuplicate(
            $this->_resource->getTableName('downloadable_sample_title'),
            $this->downloadableHelper->prepareDataForSave($this->dataSampleTitle, $titleSample)
        );
        return $this;
    }

    /**
     * Save link options
     *
     * @return $this
     */
    protected function saveLinkOptions()
    {
        $options['link'] = $this->uploadLinkFiles($this->cachedOptions['link']);
        $dataLink = $this->fillDataLink($this->dataLink, $options['link']);
        $this->connection->insertOnDuplicate(
            $this->_resource->getTableName('downloadable_link'),
            $this->downloadableHelper->prepareDataForSave($this->dataLink, $dataLink)
        );
        $dataLink = $this->fillDataTitleLink($options['link']);
        $this->connection->insertOnDuplicate(
            $this->_resource->getTableName('downloadable_link_title'),
            $this->downloadableHelper->prepareDataForSave($this->dataLinkTitle, $dataLink['title'])
        );
        if (count($dataLink['price'])) {
            $this->connection->insertOnDuplicate(
                $this->_resource->getTableName('downloadable_link_price'),
                $this->downloadableHelper->prepareDataForSave($this->dataLinkPrice, $dataLink['price'])
            );
        }
        return $this;
    }

    /**
     * Prepare string to array data sample
     *
     * @param string $rowCol
     * @param int $entityId
     * @return array
     */
    protected function prepareSampleData($rowCol, $entityId = null)
    {
        $result = [];
        $options = explode(
            ImportProduct::PSEUDO_MULTI_LINE_SEPARATOR,
            $rowCol
        );
        foreach ($options as $option) {
            // phpcs:ignore Magento2.Performance.ForeachArrayMerge
            $result[] = array_merge(
                $this->dataSample,
                ['product_id' => $entityId],
                $this->parseSampleOption(explode($this->_entityModel->getMultipleValueSeparator(), $option))
            );
        }
        return $result;
    }

    /**
     * Prepare samples data in structured format.
     *
     * @param array $samples
     * @param string|int|null $entityId
     * @return array
     */
    private function prepareStructuredSampleData(array $samples, $entityId = null): array
    {
        $result = [];

        foreach ($samples as $sample) {
            $structuredSampleOption = $this->parseStructuredSampleOption($sample);

            $temp = [];
            foreach ($this->dataSample as $key => $value) {
                $temp[$key] = $value;
            }
            $temp['product_id'] = $entityId;
            foreach ($structuredSampleOption as $key => $value) {
                $temp[$key] = $value;
            }

            $result[] = $temp;
        }

        return $result;
    }

    /**
     * Prepare string to array data link
     *
     * @param string $rowCol
     * @param string $entityId
     * @return array
     */
    protected function prepareLinkData($rowCol, $entityId = null)
    {
        $result = [];
        $options = explode(
            ImportProduct::PSEUDO_MULTI_LINE_SEPARATOR,
            $rowCol
        );
        foreach ($options as $option) {
            // phpcs:ignore Magento2.Performance.ForeachArrayMerge
            $result[] = array_merge(
                $this->dataLink,
                ['product_id' => $entityId],
                $this->parseLinkOption(explode($this->_entityModel->getMultipleValueSeparator(), $option))
            );
        }
        return $result;
    }

    /**
     * Prepare links data in structured format.
     *
     * @param array $links
     * @param string|int|null $entityId
     * @return array
     */
    private function prepareStructuredLinkData(array $links, $entityId = null): array
    {
        $result = [];

        foreach ($links as $link) {
            $linkOptions = $this->parseStructuredLinkOption($link);
            $newLink = $this->dataLink;

            foreach ($linkOptions as $key => $value) {
                $newLink[$key] = $value;
            }

            $newLink['product_id'] = $entityId;
            $result[] = $newLink;
        }

        return $result;
    }

    /**
     * Parse the link option.
     *
     * @param array $values
     * @return array
     */
    protected function parseLinkOption(array $values): array
    {
        $option = [];

        foreach ($values as $keyValue) {
            $keyValue = trim($keyValue);
            $pos = strpos($keyValue, self::PAIR_VALUE_SEPARATOR);

            if ($pos !== false) {
                $key = substr($keyValue, 0, $pos);
                $value = substr($keyValue, $pos + 1);

                $option = $this->processLinkOptionKeyValue($option, $key, $value);
            }
        }
        return $option;
    }

    /**
     * Parse link option data in structured format.
     *
     * @param array $linkOption
     * @return array
     */
    private function parseStructuredLinkOption(array $linkOption): array
    {
        $option = [];

        foreach ($linkOption as $key => $value) {
            $option = $this->processLinkOptionKeyValue($option, $key, $value);
        }

        return $option;
    }

    /**
     * Process link option key value.
     *
     * @param array $option
     * @param string $key
     * @param string $value
     * @return array
     */
    private function processLinkOptionKeyValue(array $option, string $key, string $value): array
    {
        if ($key === 'sample') {
            $option['sample_type'] = $this->downloadableHelper->getTypeByValue($value);
            $option['sample_' . $option['sample_type']] = $value;
        }
        if ($key === self::URL_OPTION_VALUE || $key === self::FILE_OPTION_VALUE) {
            $option['link_type'] = $key;
        }
        if ($key === 'downloads' && $value === 'unlimited') {
            $value = 0;
        }
        if (isset($this->optionLinkMapping[$key])) {
            $key = $this->optionLinkMapping[$key];
        }
        $option[$key] = $value;

        return $option;
    }

    /**
     * Parse the sample option.
     *
     * @param array $values
     * @return array
     */
    protected function parseSampleOption($values)
    {
        $option = [];
        foreach ($values as $keyValue) {
            $keyValue = trim($keyValue);
            $pos = strpos($keyValue, self::PAIR_VALUE_SEPARATOR);
            if ($pos !== false) {
                $key = substr($keyValue, 0, $pos);
                $value = substr($keyValue, $pos + 1);
                if ($key == self::URL_OPTION_VALUE || $key == self::FILE_OPTION_VALUE) {
                    $option['sample_type'] = $key;
                }
                if (isset($this->optionSampleMapping[$key])) {
                    $key = $this->optionSampleMapping[$key];
                }
                $option[$key] = $value;
            }
        }
        return $option;
    }

    /**
     * Parse sample option data in structured format.
     *
     * @param array $sampleOption
     * @return array
     */
    private function parseStructuredSampleOption(array $sampleOption): array
    {
        $option = [];
        foreach ($sampleOption as $key => $value) {
            if ($key == self::URL_OPTION_VALUE || $key == self::FILE_OPTION_VALUE) {
                $option['sample_type'] = $key;
            }
            if (isset($this->optionSampleMapping[$key])) {
                $key = $this->optionSampleMapping[$key];
            }
            $option[$key] = $value;
        }

        return $option;
    }

    /**
     * Uploading files into the "downloadable/files" media folder.
     *
     * Return a new file name if the same file is already exists.
     *
     * @param string $fileName
     * @param string $type
     * @param bool $renameFileOff
     * @return string
     */
    protected function uploadDownloadableFiles($fileName, $type = 'links', $renameFileOff = false)
    {
        try {
            $uploader = $this->uploaderHelper->getUploader($type, $this->parameters);
            if (!$this->uploaderHelper->isFileExist($fileName)) {
                $res = $uploader->move($fileName, $renameFileOff);
                $fileName = $res['file'];
            }
        } catch (\Exception $e) {
            $this->_entityModel->addRowError(self::ERROR_MOVE_FILE, $this->rowNum);
            $fileName = '';
        }
        return $fileName;
    }

    /**
     * Clear cached values between bunches
     *
     * @return \Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType
     */
    protected function clear()
    {
        $this->cachedOptions = [
            'link' => [],
            'sample' => []
        ];
        $this->productIds = [];
        return $this;
    }

    /**
     * Does link contain url not in whitelist?
     *
     * @param array $link
     * @param string $linkTypeKey
     * @param string $linkUrlKey
     * @return bool
     */
    private function hasDomainNotInWhitelist(array $link, string $linkTypeKey, string $linkUrlKey): bool
    {
        return (
            isset($link[$linkTypeKey]) &&
            $link[$linkTypeKey] === 'url' &&
            isset($link[$linkUrlKey]) &&
            strlen($link[$linkUrlKey]) &&
            !$this->domainValidator->isValid($link[$linkUrlKey])
        );
    }
}
