<commit_before><?php

namespace LizardsAndPumpkins\Projection\Catalog;

use LizardsAndPumpkins\Http\HttpUrl;
use LizardsAndPumpkins\Product\PriceSnippetRenderer;
use LizardsAndPumpkins\Product\ProductAttribute;
use LizardsAndPumpkins\Product\ProductAttributeList;
use LizardsAndPumpkins\Product\ProductImage\ProductImage;
use LizardsAndPumpkins\Product\ProductImage\ProductImageFileLocator;
use LizardsAndPumpkins\Product\SimpleProduct;
use LizardsAndPumpkins\Utils\ImageStorage\Image;

abstract class AbstractProductView implements ProductView
{
    /**
     * @var ProductAttributeList
     */
    private $memoizedProductAttributes;

    /**
     * @return ProductImageFileLocator
     */
    abstract protected function getProductImageFileLocator();

    /**
     * {@inheritdoc}
     */
    public function getId()
    {
        return $this->getOriginalProduct()->getId();
    }

    /**
     * {@inheritdoc}
     */
    public function getFirstValueOfAttribute($attributeCode)
    {
        $attributeValues = $this->getAllValuesOfAttribute($attributeCode);

        if (count($attributeValues) === 0) {
            return '';
        }

        return $attributeValues[0];
    }

    /**
     * {@inheritdoc}
     */
    public function getAllValuesOfAttribute($attributeCode)
    {
        $attributeList = $this->getAttributes();

        if (!$attributeList->hasAttribute($attributeCode)) {
            return [];
        }

        return array_map(function (ProductAttribute $productAttribute) {
            return $productAttribute->getValue();
        }, $attributeList->getAttributesWithCode($attributeCode));
    }

    /**
     * {@inheritdoc}
     */
    public function hasAttribute($attributeCode)
    {
        return $this->getAttributes()->hasAttribute($attributeCode);
    }

    /**
     * {@inheritdoc}
     */
    public function getAttributes()
    {
        if (null === $this->memoizedProductAttributes) {
            $attributesArray = $this->getOriginalProduct()->getAttributes()->getAllAttributes();
            $filteredAttributes = array_filter($attributesArray, [$this, 'isAttributePublic']);
            $processedAttributes = array_map([$this, 'getProcessedAttribute'], $filteredAttributes);
            $this->memoizedProductAttributes = new ProductAttributeList(...$processedAttributes);
        }
        return $this->memoizedProductAttributes;
    }

    /**
     * @param ProductAttribute $attribute
     * @return bool
     */
    protected function isAttributePublic(ProductAttribute $attribute)
    {
        return !in_array($attribute->getCode(), [PriceSnippetRenderer::PRICE, PriceSnippetRenderer::SPECIAL_PRICE]);
    }

    /**
     * @param ProductAttribute $attribute
     * @return ProductAttribute
     */
    protected function getProcessedAttribute(ProductAttribute $attribute)
    {
        // Hook method to allow the processing of attribute values
        return $attribute;
    }

    /**
     * {@inheritdoc}
     */
    public function getContext()
    {
        return $this->getOriginalProduct()->getContext();
    }

    /**
     * @param ProductImage $productImage
     * @param string $variation
     * @return Image
     */
    private function convertImage(ProductImage $productImage, $variation)
    {
        return $this->getProductImageFileLocator()->get(
            $productImage->getFileName(),
            $variation,
            $this->getContext()
        );
    }

    /**
     * {@inheritdoc}
     */
    public function getImages($variantCode)
    {
        return array_map(function (ProductImage $productImage) use ($variantCode) {
            return $this->convertImage($productImage, $variantCode);
        }, iterator_to_array($this->getOriginalProduct()->getImages()));
    }

    /**
     * {@inheritdoc}
     */
    public function getImageCount()
    {
        return $this->getOriginalProduct()->getImageCount();
    }

    /**
     * {@inheritdoc}
     */
    public function getImageByNumber($imageNumber, $variantCode)
    {
        return $imageNumber > $this->getImageCount() ?
            $this->getPlaceholderImage($variantCode) :
            $this->convertImage($this->getOriginalProduct()->getImageByNumber($imageNumber), $variantCode);
    }

    /**
     * @param int $imageNumber
     * @param string $variantCode
     * @return HttpUrl
     */
    public function getImageUrlByNumber($imageNumber, $variantCode)
    {
        return $this->getImageByNumber($imageNumber, $variantCode)->getUrl($this->getContext());
    }

    /**
     * {@inheritdoc}
     */
    public function getImageLabelByNumber($imageNumber)
    {
        return $imageNumber > $this->getImageCount() ?
            $this->getPlaceholderImageLabel() :
            $this->getOriginalProduct()->getImageLabelByNumber($imageNumber);
    }

    /**
     * @param string $variantCode
     * @return HttpUrl
     */
    public function getMainImageUrl($variantCode)
    {
        return $this->getImageCount() === 0 ?
            $this->getPlaceholderImageUrl($variantCode) :
            $this->getImageUrlByNumber(0, $variantCode);
    }

    /**
     * {@inheritdoc}
     */
    public function getMainImageLabel()
    {
        return $this->getImageCount() === 0 ?
            $this->getPlaceholderImageLabel() :
            $this->getOriginalProduct()->getMainImageLabel();
    }

    /**
     * {@inheritdoc}
     */
    public function jsonSerialize()
    {
        $original = $this->getOriginalProduct()->jsonSerialize();
        return $this->transformProductJson($original);
    }

    /**
     * @param mixed[] $productData
     * @return mixed[]
     */
    protected function transformProductJson(array $productData)
    {
        return array_reduce(array_keys($productData), function (array $carry, $key) use ($productData) {
            switch ($key) {
                case SimpleProduct::CONTEXT:
                    $result = [];
                    break;

                case 'attributes':
                    $attributes = $this->getAttributes()->jsonSerialize();
                    $result = [$key => $this->transformAttributeData($attributes)];
                    break;

                case 'images':
                    $result = ['images' => $this->getAllProductImageUrls()];
                    break;

                default:
                    $result = [$key => $productData[$key]];
                    break;
            }
            return array_merge($carry, $result);
        }, []);
    }

    /**
     * @param array[] $attributes
     * @return array[]
     */
    private function transformAttributeData(array $attributes)
    {
        return array_reduce($attributes, function (array $carry, array $attribute) {
            $code = $attribute[ProductAttribute::CODE];
            return array_merge($carry, [$code => $this->getAttributeValue($attribute, $carry)]);
        }, []);
    }

    /**
     * @param mixed[] $attribute
     * @param string[] $carry
     * @return string|string[]
     */
    private function getAttributeValue(array $attribute, array $carry)
    {<commit_msg>Issue #589: Rename method to better reveal intention<commit_after><?php

namespace LizardsAndPumpkins\Projection\Catalog;

use LizardsAndPumpkins\Http\HttpUrl;
use LizardsAndPumpkins\Product\PriceSnippetRenderer;
use LizardsAndPumpkins\Product\ProductAttribute;
use LizardsAndPumpkins\Product\ProductAttributeList;
use LizardsAndPumpkins\Product\ProductImage\ProductImage;
use LizardsAndPumpkins\Product\ProductImage\ProductImageFileLocator;
use LizardsAndPumpkins\Product\SimpleProduct;
use LizardsAndPumpkins\Utils\ImageStorage\Image;

abstract class AbstractProductView implements ProductView
{
    /**
     * @var ProductAttributeList
     */
    private $memoizedProductAttributes;

    /**
     * @return ProductImageFileLocator
     */
    abstract protected function getProductImageFileLocator();

    /**
     * {@inheritdoc}
     */
    public function getId()
    {
        return $this->getOriginalProduct()->getId();
    }

    /**
     * {@inheritdoc}
     */
    public function getFirstValueOfAttribute($attributeCode)
    {
        $attributeValues = $this->getAllValuesOfAttribute($attributeCode);

        if (count($attributeValues) === 0) {
            return '';
        }

        return $attributeValues[0];
    }

    /**
     * {@inheritdoc}
     */
    public function getAllValuesOfAttribute($attributeCode)
    {
        $attributeList = $this->getAttributes();

        if (!$attributeList->hasAttribute($attributeCode)) {
            return [];
        }

        return array_map(function (ProductAttribute $productAttribute) {
            return $productAttribute->getValue();
        }, $attributeList->getAttributesWithCode($attributeCode));
    }

    /**
     * {@inheritdoc}
     */
    public function hasAttribute($attributeCode)
    {
        return $this->getAttributes()->hasAttribute($attributeCode);
    }

    /**
     * {@inheritdoc}
     */
    public function getAttributes()
    {
        if (null === $this->memoizedProductAttributes) {
            $attributesArray = $this->getOriginalProduct()->getAttributes()->getAllAttributes();
            $filteredAttributes = array_filter($attributesArray, [$this, 'isAttributePublic']);
            $processedAttributes = array_map([$this, 'getProcessedAttribute'], $filteredAttributes);
            $this->memoizedProductAttributes = new ProductAttributeList(...$processedAttributes);
        }
        return $this->memoizedProductAttributes;
    }

    /**
     * @param ProductAttribute $attribute
     * @return bool
     */
    protected function isAttributePublic(ProductAttribute $attribute)
    {
        return !in_array($attribute->getCode(), [PriceSnippetRenderer::PRICE, PriceSnippetRenderer::SPECIAL_PRICE]);
    }

    /**
     * @param ProductAttribute $attribute
     * @return ProductAttribute
     */
    protected function getProcessedAttribute(ProductAttribute $attribute)
    {
        // Hook method to allow the processing of attribute values
        return $attribute;
    }

    /**
     * {@inheritdoc}
     */
    public function getContext()
    {
        return $this->getOriginalProduct()->getContext();
    }

    /**
     * @param ProductImage $productImage
     * @param string $variation
     * @return Image
     */
    private function convertImage(ProductImage $productImage, $variation)
    {
        return $this->getProductImageFileLocator()->get(
            $productImage->getFileName(),
            $variation,
            $this->getContext()
        );
    }

    /**
     * {@inheritdoc}
     */
    public function getImages($variantCode)
    {
        return array_map(function (ProductImage $productImage) use ($variantCode) {
            return $this->convertImage($productImage, $variantCode);
        }, iterator_to_array($this->getOriginalProduct()->getImages()));
    }

    /**
     * {@inheritdoc}
     */
    public function getImageCount()
    {
        return $this->getOriginalProduct()->getImageCount();
    }

    /**
     * {@inheritdoc}
     */
    public function getImageByNumber($imageNumber, $variantCode)
    {
        return $imageNumber > $this->getImageCount() ?
            $this->getPlaceholderImage($variantCode) :
            $this->convertImage($this->getOriginalProduct()->getImageByNumber($imageNumber), $variantCode);
    }

    /**
     * @param int $imageNumber
     * @param string $variantCode
     * @return HttpUrl
     */
    public function getImageUrlByNumber($imageNumber, $variantCode)
    {
        return $this->getImageByNumber($imageNumber, $variantCode)->getUrl($this->getContext());
    }

    /**
     * {@inheritdoc}
     */
    public function getImageLabelByNumber($imageNumber)
    {
        return $imageNumber > $this->getImageCount() ?
            $this->getPlaceholderImageLabel() :
            $this->getOriginalProduct()->getImageLabelByNumber($imageNumber);
    }

    /**
     * @param string $variantCode
     * @return HttpUrl
     */
    public function getMainImageUrl($variantCode)
    {
        return $this->getImageCount() === 0 ?
            $this->getPlaceholderImageUrl($variantCode) :
            $this->getImageUrlByNumber(0, $variantCode);
    }

    /**
     * {@inheritdoc}
     */
    public function getMainImageLabel()
    {
        return $this->getImageCount() === 0 ?
            $this->getPlaceholderImageLabel() :
            $this->getOriginalProduct()->getMainImageLabel();
    }

    /**
     * {@inheritdoc}
     */
    public function jsonSerialize()
    {
        $original = $this->getOriginalProduct()->jsonSerialize();
        return $this->transformProductJson($original);
    }

    /**
     * @param mixed[] $productData
     * @return mixed[]
     */
    protected function transformProductJson(array $productData)
    {
        return array_reduce(array_keys($productData), function (array $carry, $key) use ($productData) {
            switch ($key) {
                case SimpleProduct::CONTEXT:
                    $result = [];
                    break;

                case 'attributes':
                    $attributes = $this->getAttributes()->jsonSerialize();
                    $result = [$key => $this->transformAttributeDataToKeyValueMap($attributes)];
                    break;

                case 'images':
                    $result = ['images' => $this->getAllProductImageUrls()];
                    break;

                default:
                    $result = [$key => $productData[$key]];
                    break;
            }
            return array_merge($carry, $result);
        }, []);
    }

    /**
     * @param array[] $attributes
     * @return array[]
     */
    private function transformAttributeDataToKeyValueMap(array $attributes)
    {
        return array_reduce($attributes, function (array $carry, array $attribute) {
            $code = $attribute[ProductAttribute::CODE];
            return array_merge($carry, [$code => $this->getAttributeValue($attribute, $carry)]);
        }, []);
    }

    /**
     * @param mixed[] $attribute
     * @param string[] $carry
     * @return string|string[]
     */
    private function getAttributeValue(array $attribute, array $carry)
    {
{'fraction_non_alphanumeric': 0.12730200966318886, 'fraction_numerical': 0.0009103003991317134, 'mean_word_length': 2.464822901504124, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 123, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5892156', 'n_tokens_mistral': 3798, 'n_tokens_neox': 3710, 'n_words': 649}