<?php
/**
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade Smile ElasticSuite to newer
 * versions in the future.
 *
 * @category  Smile
 * @package   Smile\ElasticsuiteCatalog
 * @author    Aurelien FOUCRET <aurelien.foucret@smile.fr>
 * @copyright 2020 Smile
 * @license   Open Software License ("OSL") v. 3.0
 */

namespace Smile\ElasticsuiteCatalog\Model\ResourceModel\Product\Indexer\Fulltext\Action;

use Smile\ElasticsuiteCatalog\Model\ResourceModel\Eav\Indexer\Indexer;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\App\ResourceConnection;

/**
 * Elasticsearch product full indexer resource model.
 *
 * @category  Smile
 * @package   Smile\ElasticsuiteCatalog
 * @author    Aurelien FOUCRET <aurelien.foucret@smile.fr>
 */
class Full extends Indexer
{
    /**
     * @var ObjectManagerInterface
     */
    private $objectManager;

    /**
     * Indexer constructor.
     *
     * @param ResourceConnection     $resource      Resource Connection
     * @param StoreManagerInterface  $storeManager  Store Manager
     * @param MetadataPool           $metadataPool  Metadata Pool
     * @param ObjectManagerInterface $objectManager Object Manager
     */
    public function __construct(
        ResourceConnection $resource,
        StoreManagerInterface $storeManager,
        MetadataPool $metadataPool,
        ObjectManagerInterface $objectManager
    ) {
        parent::__construct($resource, $storeManager, $metadataPool);
        $this->objectManager = $objectManager;
    }

    /**
     * Load a bulk of product data.
     *
     * @param int     $storeId    Store id.
     * @param string  $productIds Product ids filter.
     * @param integer $fromId     Load product with id greater than.
     * @param integer $limit      Number of product to get loaded.
     *
     * @return array
     */
    public function getSearchableProducts($storeId, $productIds = null, $fromId = 0, $limit = 1000)
    {
        $select = $this->getConnection()->select()
            ->from(['e' => $this->getTable('catalog_product_entity')]);

        $this->addIsVisibleInStoreFilter($select, $storeId);

        if ($productIds !== null) {
            $select->where('e.entity_id IN (?)', $productIds);
        }

        $select->limit($limit);
        $select->where('e.entity_id > ?', $fromId);
        $select->order('e.entity_id');

        return $this->connection->fetchAll($select);
    }

    /**
     * Retrieve products relations by childrens
     *
     * @param array $childrenIds The product ids being reindexed
     *
     * @return array
     */
    public function getRelationsByChild($childrenIds)
    {
        $metadata      = $this->getEntityMetaData(\Magento\Catalog\Api\Data\ProductInterface::class);
        $entityTable   = $this->getTable($metadata->getEntityTable());
        $relationTable = $this->getTable('catalog_product_relation');
        $joinCondition = sprintf('relation.parent_id = entity.%s', $metadata->getLinkField());

        $select = $this->getConnection()->select()
            ->from(['relation' => $relationTable], [])
            ->join(['entity' => $entityTable], $joinCondition, [$metadata->getIdentifierField()])
            ->where('child_id IN(?)', array_map('intval', $childrenIds));

        return $this->getConnection()->fetchCol($select);
    }

    /**
     * Filter the select to append only product visible into the catalog or search into the index.
     *
     * Note : Magento put only enabled products that have the following
     *        visibility into the catalog_category_product_index table :
     *         - visible in catalog
     *         - visible in search
     *         - visible in catalog & search
     *
     *        So joining on the root category will filter only products that have to be indexed
     *        and you don't need to put any additional filter on the visibility field.
     *
     * @param \Zend_Db_Select $select  Product select to be filtered.
     * @param integer         $storeId Store Id
     *
     * @return \Smile\ElasticsuiteCatalog\Model\ResourceModel\Product\Indexer\Fulltext\Action\Full Self Reference
     */
    private function addIsVisibleInStoreFilter($select, $storeId)
    {
        $rootCategoryId = $this->getRootCategoryId($storeId);
        $indexTable     = $this->getCategoryProductIndexTable($storeId);

        $visibilityJoinCond = $this->getConnection()->quoteInto(
            'visibility.product_id = e.entity_id AND visibility.store_id = ?',
            $storeId
        );

        $select->useStraightJoin(true)
            ->join(['visibility' => $indexTable], $visibilityJoinCond, ['visibility'])
            ->where('visibility.category_id = ?', (int) $rootCategoryId);

        return $this;
    }

    /**
     * Retrieve category/product index table.
     *
     * @param int $storeId The store Id
     *
     * @return string
     */
    private function getCategoryProductIndexTable($storeId)
    {
        // Init table name as legacy table name.
        $indexTable = $this->getTable('catalog_category_product_index');

        try {
            // Retrieve table name for the current store Id from the TableMaintainer.
            $tableMaintainer = $this->objectManager->get(\Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer::class);
            $indexTable      = $tableMaintainer->getMainTable($storeId);
        } catch (\Exception $exception) {
            // Occurs in Magento version where TableMaintainer is not implemented. Will default to legacy table.
        }

        return $indexTable;
    }
}
