<?php
/**
 * Entity Preview Handler
 *
 * Handles preview functionality for all entity types.
 * Uses EntityQueryHandler for ID resolution and EntitySearchEngine for data fetching.
 *
 * @package MyPrestaRocks\EntitySelector
 */

namespace MyPrestaRocks\EntitySelector\EntitySelector;

if (!defined('_PS_VERSION_')) {
    exit;
}

use Db;
use DbQuery;
use Context;
use ImageType;

class EntityPreviewHandler
{
    /**
     * @var int Language ID
     */
    protected $idLang;

    /**
     * @var int Shop ID
     */
    protected $idShop;

    /**
     * @var EntityQueryHandler Query handler for ID resolution
     */
    protected $queryHandler;

    /**
     * @var EntitySearchEngine Search engine for entity data
     */
    protected $searchEngine;

    /**
     * @var ProductConditionResolver Product resolver
     */
    protected $productResolver;

    /**
     * Constructor
     *
     * @param int $idLang Language ID
     * @param int $idShop Shop ID
     * @param EntityQueryHandler|null $queryHandler Optional query handler
     * @param EntitySearchEngine|null $searchEngine Optional search engine
     * @param ProductConditionResolver|null $productResolver Optional product resolver
     */
    public function __construct(
        $idLang = null,
        $idShop = null,
        EntityQueryHandler $queryHandler = null,
        EntitySearchEngine $searchEngine = null,
        ProductConditionResolver $productResolver = null
    ) {
        $this->idLang = $idLang ?: (int) Context::getContext()->language->id;
        $this->idShop = $idShop ?: (int) Context::getContext()->shop->id;
        $this->queryHandler = $queryHandler ?: new EntityQueryHandler($this->idLang, $this->idShop);
        $this->searchEngine = $searchEngine ?: new EntitySearchEngine($this->idLang, $this->idShop);
        $this->productResolver = $productResolver ?: new ProductConditionResolver($this->idLang, $this->idShop);
    }

    /**
     * Preview conditions for an entity type
     *
     * @param string $entityType Entity type key
     * @param array $groups Condition groups
     * @param int $limit Preview limit
     * @param int $offset Offset for pagination
     * @return array Preview data
     */
    public function preview($entityType, array $groups, $limit = 20, $offset = 0)
    {
        // Normalize entity type (plural → singular for method names)
        $normalizedType = $this->normalizeEntityType($entityType);
        $method = 'preview' . $this->camelCase($normalizedType) . 'Conditions';

        if (method_exists($this, $method)) {
            return $this->$method($groups, $limit, $this->idLang, $this->idShop, $offset);
        }

        return ['success' => false, 'error' => 'Unknown entity type: ' . $entityType];
    }

    /**
     * Normalize entity type from block_type to method name format
     * Converts plurals to singular and handles special cases
     *
     * @param string $entityType Raw entity type from frontend
     * @return string Normalized entity type for method lookup
     */
    protected function normalizeEntityType($entityType)
    {
        // Map of block_type values (as sent by JS) to method name parts
        $typeMap = [
            'products' => 'product',
            'categories' => 'category',
            'manufacturers' => 'manufacturer',
            'suppliers' => 'supplier',
            'cms' => 'cms',
            'cms_categories' => 'cms_category',
            'employees' => 'employee',
            'customers' => 'customer',
            'customer_groups' => 'customer_group',
            'carriers' => 'carrier',
            'zones' => 'zone',
            'countries' => 'country',
            'currencies' => 'currency',
            'languages' => 'language',
            'shops' => 'shop',
            'profiles' => 'profile',
            'order_states' => 'order_state',
            'taxes' => 'tax',
        ];

        return isset($typeMap[$entityType]) ? $typeMap[$entityType] : $entityType;
    }

    /**
     * Get total count for an entity type matching conditions
     *
     * @param string $entityType Entity type key
     * @param array $groups Condition groups
     * @return int Total count
     */
    public function getPreviewCount($entityType, array $groups)
    {
        $normalizedType = $this->normalizeEntityType($entityType);

        if ($normalizedType === 'product') {
            $ids = $this->productResolver->getMatchingProductIds($groups, $this->idLang, $this->idShop);
        } else {
            $ids = $this->queryHandler->getMatchingIds($normalizedType, $groups);
        }

        return count($ids);
    }

    /**
     * Get preview counts for multiple entity types in a single call
     *
     * @param array $allConditions Array of conditions keyed by entity type
     * @return array Counts keyed by entity type
     */
    public function getPreviewCountsBulk(array $allConditions)
    {
        $counts = [];

        foreach ($allConditions as $entityType => $conditionData) {
            $groups = $conditionData['groups'] ?? [];

            try {
                $counts[$entityType] = $this->getPreviewCount($entityType, $groups);
            } catch (\Exception $e) {
                $counts[$entityType] = 0;
            }
        }

        return $counts;
    }

    /**
     * Convert snake_case to CamelCase
     *
     * @param string $string
     * @return string
     */
    protected function camelCase($string)
    {
        return str_replace(' ', '', ucwords(str_replace(['_', '-'], ' ', $string)));
    }

    /**
     * Build preview response
     *
     * @param array $allIds All matching IDs
     * @param array $items Preview items
     * @param int $limit Limit
     * @param int $offset Offset
     * @return array Response
     */
    protected function buildPreviewResponse(array $allIds, array $items, $limit, $offset)
    {
        $totalCount = count($allIds);

        return [
            'success' => true,
            'items' => $items,
            'count' => $totalCount,
            'offset' => (int) $offset,
            'limit' => (int) $limit,
            'hasMore' => ($offset + $limit) < $totalCount,
        ];
    }

    // =========================================================================
    // PRODUCTS
    // =========================================================================

    /**
     * Preview product conditions
     *
     * @param array $groups Condition groups
     * @param int $limit Preview limit
     * @param int $idLang Language ID
     * @param int $idShop Shop ID
     * @param int $offset Pagination offset
     * @return array Preview data
     */
    public function previewProductConditions($groups, $limit, $idLang, $idShop, $offset = 0)
    {
        // Get all matching product IDs
        $allIds = $this->productResolver->getMatchingProductIds($groups, $idLang, $idShop);

        if (empty($allIds)) {
            return $this->buildPreviewResponse([], [], $limit, $offset);
        }

        // Paginate IDs
        $pageIds = array_slice($allIds, (int) $offset, (int) $limit);

        // Fetch product data
        $items = $this->getProductPreviewData($pageIds, $idLang, $idShop);

        return $this->buildPreviewResponse($allIds, $items, $limit, $offset);
    }

    /**
     * Filter product IDs by search query (name, reference)
     *
     * @param array $productIds Product IDs to filter
     * @param string $query Search query
     * @param int $idLang Language ID
     * @param int $idShop Shop ID
     * @return array Filtered product IDs (preserves order)
     */
    public function filterProductIdsByQuery(array $productIds, $query, $idLang, $idShop)
    {
        if (empty($productIds) || empty($query)) {
            return $productIds;
        }

        $query = trim($query);
        if (strlen($query) < 1) {
            return $productIds;
        }

        $escapedQuery = '%' . pSQL($query) . '%';

        $sql = new DbQuery();
        $sql->select('DISTINCT p.id_product');
        $sql->from('product', 'p');
        $sql->innerJoin('product_shop', 'ps', 'ps.id_product = p.id_product AND ps.id_shop = ' . (int) $idShop);
        $sql->leftJoin('product_lang', 'pl', 'pl.id_product = p.id_product AND pl.id_lang = ' . (int) $idLang . ' AND pl.id_shop = ' . (int) $idShop);
        $sql->leftJoin('manufacturer', 'm', 'm.id_manufacturer = p.id_manufacturer');
        $sql->where('p.id_product IN (' . implode(',', array_map('intval', $productIds)) . ')');
        $sql->where('(
            pl.name LIKE \'' . $escapedQuery . '\'
            OR p.reference LIKE \'' . $escapedQuery . '\'
            OR m.name LIKE \'' . $escapedQuery . '\'
        )');

        $results = Db::getInstance()->executeS($sql);

        if (!$results) {
            return [];
        }

        $matchingIds = [];
        foreach ($results as $row) {
            $matchingIds[(int) $row['id_product']] = true;
        }

        // Preserve original order
        $filtered = [];
        foreach ($productIds as $id) {
            if (isset($matchingIds[(int) $id])) {
                $filtered[] = (int) $id;
            }
        }

        return $filtered;
    }

    /**
     * Get product preview data
     *
     * @param array $productIds Product IDs
     * @param int $idLang Language ID
     * @param int $idShop Shop ID
     * @return array Formatted preview items
     */
    public function getProductPreviewData(array $productIds, $idLang, $idShop)
    {
        if (empty($productIds)) {
            return [];
        }

        // Detect prefixed IDs (c:32, p:17) — route to combination-aware preview
        $hasPrefixed = false;
        foreach ($productIds as $id) {
            if (is_string($id) && (strpos($id, 'c:') === 0 || strpos($id, 'p:') === 0)) {
                $hasPrefixed = true;
                break;
            }
        }

        if ($hasPrefixed) {
            return $this->getProductPreviewDataPrefixed($productIds, $idLang, $idShop);
        }

        $sql = new DbQuery();
        $sql->select('p.id_product, pl.name, p.reference, p.price, p.active');
        $sql->select('sa.quantity, m.name AS manufacturer_name');
        $sql->select('cl.name AS category_name, p.id_category_default');
        $sql->select('i.id_image');
        $sql->from('product', 'p');
        $sql->innerJoin('product_shop', 'ps', 'ps.id_product = p.id_product AND ps.id_shop = ' . (int) $idShop);
        $sql->leftJoin('product_lang', 'pl', 'pl.id_product = p.id_product AND pl.id_lang = ' . (int) $idLang . ' AND pl.id_shop = ' . (int) $idShop);
        $sql->leftJoin('manufacturer', 'm', 'm.id_manufacturer = p.id_manufacturer');
        $sql->leftJoin('category_lang', 'cl', 'cl.id_category = p.id_category_default AND cl.id_lang = ' . (int) $idLang . ' AND cl.id_shop = ' . (int) $idShop);
        $sql->leftJoin('stock_available', 'sa', 'sa.id_product = p.id_product AND sa.id_product_attribute = 0 AND sa.id_shop = ' . (int) $idShop);
        $sql->leftJoin('image', 'i', 'i.id_product = p.id_product AND i.cover = 1');
        $sql->where('p.id_product IN (' . implode(',', array_map('intval', $productIds)) . ')');

        $results = Db::getInstance()->executeS($sql);

        if (!$results) {
            return [];
        }

        $imageType = $this->getProductImageType();
        $products = [];

        foreach ($results as $row) {
            $products[(int) $row['id_product']] = [
                'id' => (int) $row['id_product'],
                'name' => $row['name'],
                'reference' => $row['reference'],
                'price' => (float) $row['price'],
                'quantity' => (int) $row['quantity'],
                'active' => (bool) $row['active'],
                'manufacturer' => $row['manufacturer_name'],
                'category' => $row['category_name'],
                'image' => $row['id_image'] ? $this->getProductImageUrl($row['id_product'], $row['id_image'], $imageType) : null,
            ];
        }

        // Return in original order
        $ordered = [];
        foreach ($productIds as $id) {
            if (isset($products[(int) $id])) {
                $ordered[] = $products[(int) $id];
            }
        }

        return $ordered;
    }

    /**
     * Get preview data for prefixed IDs (c:32, p:17).
     * Splits into combination IDs and product IDs, fetches data for each.
     *
     * @param array $prefixedIds Prefixed string IDs
     * @param int $idLang Language ID
     * @param int $idShop Shop ID
     * @return array Formatted preview items in original order
     */
    protected function getProductPreviewDataPrefixed(array $prefixedIds, $idLang, $idShop)
    {
        $comboIds = [];
        $productIds = [];

        foreach ($prefixedIds as $prefixedId) {
            $prefixedId = (string) $prefixedId;
            if (strpos($prefixedId, 'c:') === 0) {
                $comboIds[] = (int) substr($prefixedId, 2);
            } elseif (strpos($prefixedId, 'p:') === 0) {
                $productIds[] = (int) substr($prefixedId, 2);
            } else {
                $productIds[] = (int) $prefixedId;
            }
        }

        $db = Db::getInstance();
        $imageType = $this->getProductImageType();
        $itemsByKey = [];

        // Fetch combination data
        if (!empty($comboIds)) {
            $sql = 'SELECT pa.id_product_attribute, pa.id_product, pl.name, p.reference AS product_reference,
                    pa.reference AS combo_reference, p.price, p.active,
                    IFNULL(sa_c.quantity, 0) AS quantity,
                    m.name AS manufacturer_name, cl.name AS category_name,
                    i.id_image, pai.id_image AS combo_id_image,
                    GROUP_CONCAT(CONCAT(agl.name, \': \', al.name) ORDER BY ag.position ASC SEPARATOR \', \') AS attributes
                FROM ' . _DB_PREFIX_ . 'product_attribute pa
                INNER JOIN ' . _DB_PREFIX_ . 'product p ON p.id_product = pa.id_product
                INNER JOIN ' . _DB_PREFIX_ . 'product_attribute_shop pas ON pas.id_product_attribute = pa.id_product_attribute AND pas.id_shop = ' . (int) $idShop . '
                INNER JOIN ' . _DB_PREFIX_ . 'product_lang pl ON pl.id_product = p.id_product AND pl.id_lang = ' . (int) $idLang . ' AND pl.id_shop = ' . (int) $idShop . '
                INNER JOIN ' . _DB_PREFIX_ . 'product_attribute_combination pac ON pac.id_product_attribute = pa.id_product_attribute
                INNER JOIN ' . _DB_PREFIX_ . 'attribute a ON a.id_attribute = pac.id_attribute
                INNER JOIN ' . _DB_PREFIX_ . 'attribute_lang al ON al.id_attribute = a.id_attribute AND al.id_lang = ' . (int) $idLang . '
                INNER JOIN ' . _DB_PREFIX_ . 'attribute_group ag ON ag.id_attribute_group = a.id_attribute_group
                INNER JOIN ' . _DB_PREFIX_ . 'attribute_group_lang agl ON agl.id_attribute_group = a.id_attribute_group AND agl.id_lang = ' . (int) $idLang . '
                LEFT JOIN ' . _DB_PREFIX_ . 'manufacturer m ON m.id_manufacturer = p.id_manufacturer
                LEFT JOIN ' . _DB_PREFIX_ . 'category_lang cl ON cl.id_category = p.id_category_default AND cl.id_lang = ' . (int) $idLang . ' AND cl.id_shop = ' . (int) $idShop . '
                LEFT JOIN ' . _DB_PREFIX_ . 'stock_available sa_c ON sa_c.id_product = pa.id_product AND sa_c.id_product_attribute = pa.id_product_attribute AND sa_c.id_shop = ' . (int) $idShop . '
                LEFT JOIN ' . _DB_PREFIX_ . 'image i ON i.id_product = p.id_product AND i.cover = 1
                LEFT JOIN ' . _DB_PREFIX_ . 'product_attribute_image pai ON pai.id_product_attribute = pa.id_product_attribute
                WHERE pa.id_product_attribute IN (' . implode(',', array_map('intval', $comboIds)) . ')
                GROUP BY pa.id_product_attribute';

            $rows = $db->executeS($sql);
            if ($rows) {
                foreach ($rows as $row) {
                    $key = 'c:' . (int) $row['id_product_attribute'];
                    $imageId = !empty($row['combo_id_image']) ? $row['combo_id_image'] : ($row['id_image'] ?? null);
                    $ref = $row['combo_reference'] ?: $row['product_reference'];

                    $itemsByKey[$key] = [
                        'id' => $key,
                        'name' => $row['name'] . ', ' . $row['attributes'],
                        'reference' => $ref,
                        'price' => (float) $row['price'],
                        'quantity' => (int) $row['quantity'],
                        'active' => (bool) $row['active'],
                        'manufacturer' => $row['manufacturer_name'],
                        'category' => $row['category_name'],
                        'image' => $imageId ? $this->getProductImageUrl($row['id_product'], $imageId, $imageType) : null,
                        'attributes' => $row['attributes'],
                        'isCombination' => true,
                    ];
                }
            }
        }

        // Fetch plain product data
        if (!empty($productIds)) {
            $sql = new DbQuery();
            $sql->select('p.id_product, pl.name, p.reference, p.price, p.active');
            $sql->select('sa.quantity, m.name AS manufacturer_name');
            $sql->select('cl.name AS category_name, i.id_image');
            $sql->from('product', 'p');
            $sql->innerJoin('product_shop', 'ps', 'ps.id_product = p.id_product AND ps.id_shop = ' . (int) $idShop);
            $sql->leftJoin('product_lang', 'pl', 'pl.id_product = p.id_product AND pl.id_lang = ' . (int) $idLang . ' AND pl.id_shop = ' . (int) $idShop);
            $sql->leftJoin('manufacturer', 'm', 'm.id_manufacturer = p.id_manufacturer');
            $sql->leftJoin('category_lang', 'cl', 'cl.id_category = p.id_category_default AND cl.id_lang = ' . (int) $idLang . ' AND cl.id_shop = ' . (int) $idShop);
            $sql->leftJoin('stock_available', 'sa', 'sa.id_product = p.id_product AND sa.id_product_attribute = 0 AND sa.id_shop = ' . (int) $idShop);
            $sql->leftJoin('image', 'i', 'i.id_product = p.id_product AND i.cover = 1');
            $sql->where('p.id_product IN (' . implode(',', array_map('intval', $productIds)) . ')');

            $rows = $db->executeS($sql);
            if ($rows) {
                foreach ($rows as $row) {
                    $key = 'p:' . (int) $row['id_product'];
                    $itemsByKey[$key] = [
                        'id' => $key,
                        'name' => $row['name'],
                        'reference' => $row['reference'],
                        'price' => (float) $row['price'],
                        'quantity' => (int) $row['quantity'],
                        'active' => (bool) $row['active'],
                        'manufacturer' => $row['manufacturer_name'],
                        'category' => $row['category_name'],
                        'image' => $row['id_image'] ? $this->getProductImageUrl($row['id_product'], $row['id_image'], $imageType) : null,
                    ];
                }
            }
        }

        // Return in original order
        $ordered = [];
        foreach ($prefixedIds as $id) {
            $key = (string) $id;
            if (isset($itemsByKey[$key])) {
                $ordered[] = $itemsByKey[$key];
            }
        }

        return $ordered;
    }

    /**
     * Get product image type for thumbnails
     *
     * @return string
     */
    protected function getProductImageType()
    {
        $type = ImageType::getFormattedName('small');
        return $type ?: 'small_default';
    }

    /**
     * Get product image URL
     *
     * @param int $idProduct Product ID
     * @param int $idImage Image ID
     * @param string $type Image type
     * @return string
     */
    protected function getProductImageUrl($idProduct, $idImage, $type)
    {
        $link = Context::getContext()->link;
        return $link->getImageLink('product', $idProduct . '-' . $idImage, $type);
    }

    // =========================================================================
    // CATEGORIES
    // =========================================================================

    /**
     * Preview category conditions
     *
     * @param array $groups Condition groups
     * @param int $limit Preview limit
     * @param int $idLang Language ID
     * @param int $idShop Shop ID
     * @param int $offset Pagination offset
     * @return array Preview data
     */
    public function previewCategoryConditions($groups, $limit, $idLang, $idShop, $offset = 0)
    {
        $allIds = $this->queryHandler->getMatchingCategoryIds($groups, $idLang, $idShop);

        if (empty($allIds)) {
            return $this->buildPreviewResponse([], [], $limit, $offset);
        }

        $pageIds = array_slice($allIds, (int) $offset, (int) $limit);
        $items = $this->getCategoryPreviewData($pageIds, $idLang, $idShop);

        return $this->buildPreviewResponse($allIds, $items, $limit, $offset);
    }

    /**
     * Get category preview data
     *
     * @param array $categoryIds Category IDs
     * @param int $idLang Language ID
     * @param int $idShop Shop ID
     * @return array Formatted preview items
     */
    public function getCategoryPreviewData(array $categoryIds, $idLang, $idShop)
    {
        if (empty($categoryIds)) {
            return [];
        }

        $sql = new DbQuery();
        $sql->select('c.id_category, cl.name, c.active, c.level_depth, c.position');
        $sql->select('(SELECT COUNT(cp.id_product) FROM ' . _DB_PREFIX_ . 'category_product cp WHERE cp.id_category = c.id_category) AS product_count');
        $sql->from('category', 'c');
        $sql->leftJoin('category_lang', 'cl', 'cl.id_category = c.id_category AND cl.id_lang = ' . (int) $idLang . ' AND cl.id_shop = ' . (int) $idShop);
        $sql->where('c.id_category IN (' . implode(',', array_map('intval', $categoryIds)) . ')');

        $results = Db::getInstance()->executeS($sql);

        if (!$results) {
            return [];
        }

        $categories = [];
        foreach ($results as $row) {
            $categories[(int) $row['id_category']] = [
                'id' => (int) $row['id_category'],
                'name' => $row['name'],
                'active' => (bool) $row['active'],
                'level_depth' => (int) $row['level_depth'],
                'position' => (int) $row['position'],
                'product_count' => (int) $row['product_count'],
            ];
        }

        $ordered = [];
        foreach ($categoryIds as $id) {
            if (isset($categories[(int) $id])) {
                $ordered[] = $categories[(int) $id];
            }
        }

        return $ordered;
    }

    // =========================================================================
    // MANUFACTURERS
    // =========================================================================

    /**
     * Preview manufacturer conditions
     */
    public function previewManufacturerConditions($groups, $limit, $idLang, $idShop, $offset = 0)
    {
        $allIds = $this->queryHandler->getMatchingManufacturerIds($groups, $idLang, $idShop);

        if (empty($allIds)) {
            return $this->buildPreviewResponse([], [], $limit, $offset);
        }

        $pageIds = array_slice($allIds, (int) $offset, (int) $limit);
        $items = $this->getManufacturerPreviewData($pageIds, $idLang, $idShop);

        return $this->buildPreviewResponse($allIds, $items, $limit, $offset);
    }

    /**
     * Get manufacturer preview data
     */
    public function getManufacturerPreviewData(array $ids, $idLang, $idShop)
    {
        if (empty($ids)) {
            return [];
        }

        $sql = new DbQuery();
        $sql->select('m.id_manufacturer, m.name, m.active');
        $sql->select('(SELECT COUNT(DISTINCT p.id_product) FROM ' . _DB_PREFIX_ . 'product p
            INNER JOIN ' . _DB_PREFIX_ . 'product_shop ps ON ps.id_product = p.id_product AND ps.id_shop = ' . (int) $idShop . '
            WHERE p.id_manufacturer = m.id_manufacturer) AS product_count');
        $sql->from('manufacturer', 'm');
        $sql->where('m.id_manufacturer IN (' . implode(',', array_map('intval', $ids)) . ')');

        $results = Db::getInstance()->executeS($sql);

        if (!$results) {
            return [];
        }

        $items = [];
        foreach ($results as $row) {
            $items[(int) $row['id_manufacturer']] = [
                'id' => (int) $row['id_manufacturer'],
                'name' => $row['name'],
                'active' => (bool) $row['active'],
                'product_count' => (int) $row['product_count'],
            ];
        }

        $ordered = [];
        foreach ($ids as $id) {
            if (isset($items[(int) $id])) {
                $ordered[] = $items[(int) $id];
            }
        }

        return $ordered;
    }

    // =========================================================================
    // SUPPLIERS
    // =========================================================================

    /**
     * Preview supplier conditions
     */
    public function previewSupplierConditions($groups, $limit, $idLang, $idShop, $offset = 0)
    {
        $allIds = $this->queryHandler->getMatchingSupplierIds($groups, $idLang, $idShop);

        if (empty($allIds)) {
            return $this->buildPreviewResponse([], [], $limit, $offset);
        }

        $pageIds = array_slice($allIds, (int) $offset, (int) $limit);
        $items = $this->getSupplierPreviewData($pageIds, $idLang, $idShop);

        return $this->buildPreviewResponse($allIds, $items, $limit, $offset);
    }

    /**
     * Get supplier preview data
     */
    public function getSupplierPreviewData(array $ids, $idLang, $idShop)
    {
        if (empty($ids)) {
            return [];
        }

        $sql = new DbQuery();
        $sql->select('s.id_supplier, s.name, s.active');
        $sql->select('(SELECT COUNT(DISTINCT ps.id_product) FROM ' . _DB_PREFIX_ . 'product_supplier ps
            INNER JOIN ' . _DB_PREFIX_ . 'product_shop psh ON psh.id_product = ps.id_product AND psh.id_shop = ' . (int) $idShop . '
            WHERE ps.id_supplier = s.id_supplier) AS product_count');
        $sql->from('supplier', 's');
        $sql->where('s.id_supplier IN (' . implode(',', array_map('intval', $ids)) . ')');

        $results = Db::getInstance()->executeS($sql);

        if (!$results) {
            return [];
        }

        $items = [];
        foreach ($results as $row) {
            $items[(int) $row['id_supplier']] = [
                'id' => (int) $row['id_supplier'],
                'name' => $row['name'],
                'active' => (bool) $row['active'],
                'product_count' => (int) $row['product_count'],
            ];
        }

        $ordered = [];
        foreach ($ids as $id) {
            if (isset($items[(int) $id])) {
                $ordered[] = $items[(int) $id];
            }
        }

        return $ordered;
    }

    // =========================================================================
    // CMS PAGES
    // =========================================================================

    /**
     * Preview CMS page conditions
     */
    public function previewCmsConditions($groups, $limit, $idLang, $idShop, $offset = 0)
    {
        $allIds = $this->queryHandler->getMatchingCmsIds($groups, $idLang, $idShop);

        if (empty($allIds)) {
            return $this->buildPreviewResponse([], [], $limit, $offset);
        }

        $pageIds = array_slice($allIds, (int) $offset, (int) $limit);
        $items = $this->getCmsPreviewData($pageIds, $idLang, $idShop);

        return $this->buildPreviewResponse($allIds, $items, $limit, $offset);
    }

    /**
     * Get CMS page preview data
     */
    public function getCmsPreviewData(array $ids, $idLang, $idShop)
    {
        if (empty($ids)) {
            return [];
        }

        $sql = new DbQuery();
        $sql->select('c.id_cms, cl.meta_title, c.active, c.position, c.id_cms_category');
        $sql->select('ccl.name AS category_name');
        $sql->from('cms', 'c');
        $sql->leftJoin('cms_lang', 'cl', 'cl.id_cms = c.id_cms AND cl.id_lang = ' . (int) $idLang . ' AND cl.id_shop = ' . (int) $idShop);
        $sql->leftJoin('cms_category_lang', 'ccl', 'ccl.id_cms_category = c.id_cms_category AND ccl.id_lang = ' . (int) $idLang . ' AND ccl.id_shop = ' . (int) $idShop);
        $sql->where('c.id_cms IN (' . implode(',', array_map('intval', $ids)) . ')');

        $results = Db::getInstance()->executeS($sql);

        if (!$results) {
            return [];
        }

        $items = [];
        foreach ($results as $row) {
            $items[(int) $row['id_cms']] = [
                'id' => (int) $row['id_cms'],
                'name' => $row['meta_title'],
                'active' => (bool) $row['active'],
                'position' => (int) $row['position'],
                'category' => $row['category_name'],
            ];
        }

        $ordered = [];
        foreach ($ids as $id) {
            if (isset($items[(int) $id])) {
                $ordered[] = $items[(int) $id];
            }
        }

        return $ordered;
    }

    // =========================================================================
    // CMS CATEGORIES
    // =========================================================================

    /**
     * Preview CMS category conditions
     */
    public function previewCmsCategoryConditions($groups, $limit, $idLang, $idShop, $offset = 0)
    {
        $allIds = $this->queryHandler->getMatchingCmsCategoryIds($groups, $idLang, $idShop);

        if (empty($allIds)) {
            return $this->buildPreviewResponse([], [], $limit, $offset);
        }

        $pageIds = array_slice($allIds, (int) $offset, (int) $limit);
        $items = $this->getCmsCategoryPreviewData($pageIds, $idLang, $idShop);

        return $this->buildPreviewResponse($allIds, $items, $limit, $offset);
    }

    /**
     * Get CMS category preview data
     */
    public function getCmsCategoryPreviewData(array $ids, $idLang, $idShop)
    {
        if (empty($ids)) {
            return [];
        }

        $sql = new DbQuery();
        $sql->select('cc.id_cms_category, ccl.name, cc.active, cc.level_depth, cc.position');
        $sql->from('cms_category', 'cc');
        $sql->leftJoin('cms_category_lang', 'ccl', 'ccl.id_cms_category = cc.id_cms_category AND ccl.id_lang = ' . (int) $idLang . ' AND ccl.id_shop = ' . (int) $idShop);
        $sql->where('cc.id_cms_category IN (' . implode(',', array_map('intval', $ids)) . ')');

        $results = Db::getInstance()->executeS($sql);

        if (!$results) {
            return [];
        }

        $items = [];
        foreach ($results as $row) {
            $items[(int) $row['id_cms_category']] = [
                'id' => (int) $row['id_cms_category'],
                'name' => $row['name'],
                'active' => (bool) $row['active'],
                'level_depth' => (int) $row['level_depth'],
                'position' => (int) $row['position'],
            ];
        }

        $ordered = [];
        foreach ($ids as $id) {
            if (isset($items[(int) $id])) {
                $ordered[] = $items[(int) $id];
            }
        }

        return $ordered;
    }

    // =========================================================================
    // EMPLOYEES
    // =========================================================================

    /**
     * Preview employee conditions
     */
    public function previewEmployeeConditions($groups, $limit, $idLang, $idShop, $offset = 0)
    {
        $allIds = $this->queryHandler->getMatchingEmployeeIds($groups, $idLang, $idShop);

        if (empty($allIds)) {
            return $this->buildPreviewResponse([], [], $limit, $offset);
        }

        $pageIds = array_slice($allIds, (int) $offset, (int) $limit);
        $items = $this->getEmployeePreviewData($pageIds, $idLang);

        return $this->buildPreviewResponse($allIds, $items, $limit, $offset);
    }

    /**
     * Get employee preview data
     */
    public function getEmployeePreviewData(array $ids, $idLang)
    {
        if (empty($ids)) {
            return [];
        }

        $sql = new DbQuery();
        $sql->select('e.id_employee, e.firstname, e.lastname, e.email, e.active, e.id_profile');
        $sql->select('pl.name AS profile_name');
        $sql->from('employee', 'e');
        $sql->leftJoin('profile_lang', 'pl', 'pl.id_profile = e.id_profile AND pl.id_lang = ' . (int) $idLang);
        $sql->where('e.id_employee IN (' . implode(',', array_map('intval', $ids)) . ')');

        $results = Db::getInstance()->executeS($sql);

        if (!$results) {
            return [];
        }

        $items = [];
        foreach ($results as $row) {
            $items[(int) $row['id_employee']] = [
                'id' => (int) $row['id_employee'],
                'name' => $row['firstname'] . ' ' . $row['lastname'],
                'email' => $row['email'],
                'active' => (bool) $row['active'],
                'profile' => $row['profile_name'],
            ];
        }

        $ordered = [];
        foreach ($ids as $id) {
            if (isset($items[(int) $id])) {
                $ordered[] = $items[(int) $id];
            }
        }

        return $ordered;
    }

    // =========================================================================
    // CUSTOMERS
    // =========================================================================

    /**
     * Preview customer conditions
     */
    public function previewCustomerConditions($groups, $limit, $idLang, $idShop, $offset = 0)
    {
        $allIds = $this->queryHandler->getMatchingCustomerIds($groups, $idLang, $idShop);

        if (empty($allIds)) {
            return $this->buildPreviewResponse([], [], $limit, $offset);
        }

        $pageIds = array_slice($allIds, (int) $offset, (int) $limit);
        $items = $this->getCustomerPreviewData($pageIds, $idLang);

        return $this->buildPreviewResponse($allIds, $items, $limit, $offset);
    }

    /**
     * Get customer preview data
     */
    public function getCustomerPreviewData(array $ids, $idLang)
    {
        if (empty($ids)) {
            return [];
        }

        $sql = new DbQuery();
        $sql->select('c.id_customer, c.firstname, c.lastname, c.email, c.active, c.newsletter, c.company');
        $sql->select('c.date_add, gl.name AS group_name');
        $sql->from('customer', 'c');
        $sql->leftJoin('group_lang', 'gl', 'gl.id_group = c.id_default_group AND gl.id_lang = ' . (int) $idLang);
        $sql->where('c.id_customer IN (' . implode(',', array_map('intval', $ids)) . ')');

        $results = Db::getInstance()->executeS($sql);

        if (!$results) {
            return [];
        }

        $items = [];
        foreach ($results as $row) {
            $items[(int) $row['id_customer']] = [
                'id' => (int) $row['id_customer'],
                'name' => $row['firstname'] . ' ' . $row['lastname'],
                'email' => $row['email'],
                'company' => $row['company'],
                'active' => (bool) $row['active'],
                'newsletter' => (bool) $row['newsletter'],
                'group' => $row['group_name'],
                'date_add' => $row['date_add'],
            ];
        }

        $ordered = [];
        foreach ($ids as $id) {
            if (isset($items[(int) $id])) {
                $ordered[] = $items[(int) $id];
            }
        }

        return $ordered;
    }

    // =========================================================================
    // CUSTOMER GROUPS
    // =========================================================================

    /**
     * Preview customer group conditions
     */
    public function previewCustomerGroupConditions($groups, $limit, $idLang, $idShop, $offset = 0)
    {
        $allIds = $this->queryHandler->getMatchingCustomerGroupIds($groups, $idLang, $idShop);

        if (empty($allIds)) {
            return $this->buildPreviewResponse([], [], $limit, $offset);
        }

        $pageIds = array_slice($allIds, (int) $offset, (int) $limit);
        $items = $this->getCustomerGroupPreviewData($pageIds, $idLang);

        return $this->buildPreviewResponse($allIds, $items, $limit, $offset);
    }

    /**
     * Get customer group preview data
     */
    public function getCustomerGroupPreviewData(array $ids, $idLang)
    {
        if (empty($ids)) {
            return [];
        }

        $sql = new DbQuery();
        $sql->select('g.id_group, gl.name, g.reduction, g.price_display_method, g.show_prices');
        $sql->select('(SELECT COUNT(cg.id_customer) FROM ' . _DB_PREFIX_ . 'customer_group cg WHERE cg.id_group = g.id_group) AS customer_count');
        $sql->from('group', 'g');
        $sql->leftJoin('group_lang', 'gl', 'gl.id_group = g.id_group AND gl.id_lang = ' . (int) $idLang);
        $sql->where('g.id_group IN (' . implode(',', array_map('intval', $ids)) . ')');

        $results = Db::getInstance()->executeS($sql);

        if (!$results) {
            return [];
        }

        $items = [];
        foreach ($results as $row) {
            $items[(int) $row['id_group']] = [
                'id' => (int) $row['id_group'],
                'name' => $row['name'],
                'reduction' => (float) $row['reduction'],
                'price_display' => (int) $row['price_display_method'],
                'show_prices' => (bool) $row['show_prices'],
                'customer_count' => (int) $row['customer_count'],
            ];
        }

        $ordered = [];
        foreach ($ids as $id) {
            if (isset($items[(int) $id])) {
                $ordered[] = $items[(int) $id];
            }
        }

        return $ordered;
    }

    // =========================================================================
    // CARRIERS
    // =========================================================================

    /**
     * Preview carrier conditions
     */
    public function previewCarrierConditions($groups, $limit, $idLang, $idShop, $offset = 0)
    {
        $allIds = $this->queryHandler->getMatchingCarrierIds($groups, $idLang, $idShop);

        if (empty($allIds)) {
            return $this->buildPreviewResponse([], [], $limit, $offset);
        }

        $pageIds = array_slice($allIds, (int) $offset, (int) $limit);
        $items = $this->getCarrierPreviewData($pageIds, $idLang);

        return $this->buildPreviewResponse($allIds, $items, $limit, $offset);
    }

    /**
     * Get carrier preview data
     */
    public function getCarrierPreviewData(array $ids, $idLang)
    {
        if (empty($ids)) {
            return [];
        }

        $sql = new DbQuery();
        $sql->select('c.id_carrier, c.id_reference, c.name, c.active, c.is_free, c.shipping_handling');
        $sql->from('carrier', 'c');
        $sql->where('c.id_carrier IN (' . implode(',', array_map('intval', $ids)) . ')');

        $results = Db::getInstance()->executeS($sql);

        if (!$results) {
            return [];
        }

        $items = [];
        foreach ($results as $row) {
            $items[(int) $row['id_carrier']] = [
                'id' => (int) $row['id_carrier'],
                'id_reference' => (int) $row['id_reference'],
                'name' => $row['name'],
                'active' => (bool) $row['active'],
                'is_free' => (bool) $row['is_free'],
                'shipping_handling' => (bool) $row['shipping_handling'],
            ];
        }

        $ordered = [];
        foreach ($ids as $id) {
            if (isset($items[(int) $id])) {
                $ordered[] = $items[(int) $id];
            }
        }

        return $ordered;
    }

    // =========================================================================
    // ZONES
    // =========================================================================

    /**
     * Preview zone conditions
     */
    public function previewZoneConditions($groups, $limit, $idLang, $idShop, $offset = 0)
    {
        $allIds = $this->queryHandler->getMatchingZoneIds($groups, $idLang, $idShop);

        if (empty($allIds)) {
            return $this->buildPreviewResponse([], [], $limit, $offset);
        }

        $pageIds = array_slice($allIds, (int) $offset, (int) $limit);
        $items = $this->getZonePreviewData($pageIds, $idLang);

        return $this->buildPreviewResponse($allIds, $items, $limit, $offset);
    }

    /**
     * Get zone preview data
     */
    public function getZonePreviewData(array $ids, $idLang)
    {
        if (empty($ids)) {
            return [];
        }

        $sql = new DbQuery();
        $sql->select('z.id_zone, z.name, z.active');
        $sql->from('zone', 'z');
        $sql->where('z.id_zone IN (' . implode(',', array_map('intval', $ids)) . ')');

        $results = Db::getInstance()->executeS($sql);

        if (!$results) {
            return [];
        }

        $items = [];
        foreach ($results as $row) {
            $items[(int) $row['id_zone']] = [
                'id' => (int) $row['id_zone'],
                'name' => $row['name'],
                'active' => (bool) $row['active'],
            ];
        }

        $ordered = [];
        foreach ($ids as $id) {
            if (isset($items[(int) $id])) {
                $ordered[] = $items[(int) $id];
            }
        }

        return $ordered;
    }

    // =========================================================================
    // COUNTRIES
    // =========================================================================

    /**
     * Preview country conditions
     */
    public function previewCountryConditions($groups, $limit, $idLang, $idShop, $offset = 0)
    {
        $allIds = $this->queryHandler->getMatchingCountryIds($groups, $idLang, $idShop);

        if (empty($allIds)) {
            return $this->buildPreviewResponse([], [], $limit, $offset);
        }

        $pageIds = array_slice($allIds, (int) $offset, (int) $limit);
        $items = $this->getCountryPreviewData($pageIds, $idLang);

        return $this->buildPreviewResponse($allIds, $items, $limit, $offset);
    }

    /**
     * Get country preview data
     */
    public function getCountryPreviewData(array $ids, $idLang)
    {
        if (empty($ids)) {
            return [];
        }

        $sql = new DbQuery();
        $sql->select('c.id_country, cl.name, c.iso_code, c.active, c.id_zone');
        $sql->select('z.name AS zone_name');
        $sql->from('country', 'c');
        $sql->leftJoin('country_lang', 'cl', 'cl.id_country = c.id_country AND cl.id_lang = ' . (int) $idLang);
        $sql->leftJoin('zone', 'z', 'z.id_zone = c.id_zone');
        $sql->where('c.id_country IN (' . implode(',', array_map('intval', $ids)) . ')');

        $results = Db::getInstance()->executeS($sql);

        if (!$results) {
            return [];
        }

        $items = [];
        foreach ($results as $row) {
            $items[(int) $row['id_country']] = [
                'id' => (int) $row['id_country'],
                'name' => $row['name'],
                'iso_code' => $row['iso_code'],
                'active' => (bool) $row['active'],
                'zone' => $row['zone_name'],
            ];
        }

        $ordered = [];
        foreach ($ids as $id) {
            if (isset($items[(int) $id])) {
                $ordered[] = $items[(int) $id];
            }
        }

        return $ordered;
    }

    // =========================================================================
    // CURRENCIES
    // =========================================================================

    /**
     * Preview currency conditions
     */
    public function previewCurrencyConditions($groups, $limit, $idLang, $idShop, $offset = 0)
    {
        $allIds = $this->queryHandler->getMatchingCurrencyIds($groups, $idLang, $idShop);

        if (empty($allIds)) {
            return $this->buildPreviewResponse([], [], $limit, $offset);
        }

        $pageIds = array_slice($allIds, (int) $offset, (int) $limit);
        $items = $this->getCurrencyPreviewData($pageIds, $idLang);

        return $this->buildPreviewResponse($allIds, $items, $limit, $offset);
    }

    /**
     * Get currency preview data
     */
    public function getCurrencyPreviewData(array $ids, $idLang)
    {
        if (empty($ids)) {
            return [];
        }

        $sql = new DbQuery();
        $sql->select('c.id_currency, cl.name, c.iso_code, c.active, c.conversion_rate, cl.symbol');
        $sql->from('currency', 'c');
        $sql->leftJoin('currency_lang', 'cl', 'cl.id_currency = c.id_currency AND cl.id_lang = ' . (int) $idLang);
        $sql->where('c.id_currency IN (' . implode(',', array_map('intval', $ids)) . ')');

        $results = Db::getInstance()->executeS($sql);

        if (!$results) {
            return [];
        }

        $items = [];
        foreach ($results as $row) {
            $items[(int) $row['id_currency']] = [
                'id' => (int) $row['id_currency'],
                'name' => $row['name'],
                'iso_code' => $row['iso_code'],
                'sign' => $row['symbol'],
                'active' => (bool) $row['active'],
                'conversion_rate' => (float) $row['conversion_rate'],
            ];
        }

        $ordered = [];
        foreach ($ids as $id) {
            if (isset($items[(int) $id])) {
                $ordered[] = $items[(int) $id];
            }
        }

        return $ordered;
    }

    // =========================================================================
    // LANGUAGES
    // =========================================================================

    /**
     * Preview language conditions
     */
    public function previewLanguageConditions($groups, $limit, $idLang, $idShop, $offset = 0)
    {
        $allIds = $this->queryHandler->getMatchingLanguageIds($groups, $idLang, $idShop);

        if (empty($allIds)) {
            return $this->buildPreviewResponse([], [], $limit, $offset);
        }

        $pageIds = array_slice($allIds, (int) $offset, (int) $limit);
        $items = $this->getLanguagePreviewData($pageIds, $idLang);

        return $this->buildPreviewResponse($allIds, $items, $limit, $offset);
    }

    /**
     * Get language preview data
     */
    public function getLanguagePreviewData(array $ids, $idLang)
    {
        if (empty($ids)) {
            return [];
        }

        $sql = new DbQuery();
        $sql->select('l.id_lang, l.name, l.iso_code, l.active, l.is_rtl');
        $sql->from('lang', 'l');
        $sql->where('l.id_lang IN (' . implode(',', array_map('intval', $ids)) . ')');

        $results = Db::getInstance()->executeS($sql);

        if (!$results) {
            return [];
        }

        $items = [];
        foreach ($results as $row) {
            $items[(int) $row['id_lang']] = [
                'id' => (int) $row['id_lang'],
                'name' => $row['name'],
                'iso_code' => $row['iso_code'],
                'active' => (bool) $row['active'],
                'is_rtl' => (bool) $row['is_rtl'],
            ];
        }

        $ordered = [];
        foreach ($ids as $id) {
            if (isset($items[(int) $id])) {
                $ordered[] = $items[(int) $id];
            }
        }

        return $ordered;
    }

    // =========================================================================
    // SHOPS
    // =========================================================================

    /**
     * Preview shop conditions
     */
    public function previewShopConditions($groups, $limit, $idLang, $idShop, $offset = 0)
    {
        $allIds = $this->queryHandler->getMatchingShopIds($groups, $idLang, $idShop);

        if (empty($allIds)) {
            return $this->buildPreviewResponse([], [], $limit, $offset);
        }

        $pageIds = array_slice($allIds, (int) $offset, (int) $limit);
        $items = $this->getShopPreviewData($pageIds, $idLang);

        return $this->buildPreviewResponse($allIds, $items, $limit, $offset);
    }

    /**
     * Get shop preview data
     */
    public function getShopPreviewData(array $ids, $idLang)
    {
        if (empty($ids)) {
            return [];
        }

        $sql = new DbQuery();
        $sql->select('s.id_shop, s.name, s.active, s.id_shop_group');
        $sql->select('sg.name AS group_name');
        $sql->from('shop', 's');
        $sql->leftJoin('shop_group', 'sg', 'sg.id_shop_group = s.id_shop_group');
        $sql->where('s.id_shop IN (' . implode(',', array_map('intval', $ids)) . ')');

        $results = Db::getInstance()->executeS($sql);

        if (!$results) {
            return [];
        }

        $items = [];
        foreach ($results as $row) {
            $items[(int) $row['id_shop']] = [
                'id' => (int) $row['id_shop'],
                'name' => $row['name'],
                'active' => (bool) $row['active'],
                'group' => $row['group_name'],
            ];
        }

        $ordered = [];
        foreach ($ids as $id) {
            if (isset($items[(int) $id])) {
                $ordered[] = $items[(int) $id];
            }
        }

        return $ordered;
    }

    // =========================================================================
    // PROFILES
    // =========================================================================

    /**
     * Preview profile conditions
     */
    public function previewProfileConditions($groups, $limit, $idLang, $idShop, $offset = 0)
    {
        $allIds = $this->queryHandler->getMatchingProfileIds($groups, $idLang, $idShop);

        if (empty($allIds)) {
            return $this->buildPreviewResponse([], [], $limit, $offset);
        }

        $pageIds = array_slice($allIds, (int) $offset, (int) $limit);
        $items = $this->getProfilePreviewData($pageIds, $idLang);

        return $this->buildPreviewResponse($allIds, $items, $limit, $offset);
    }

    /**
     * Get profile preview data
     */
    public function getProfilePreviewData(array $ids, $idLang)
    {
        if (empty($ids)) {
            return [];
        }

        $sql = new DbQuery();
        $sql->select('p.id_profile, pl.name');
        $sql->from('profile', 'p');
        $sql->leftJoin('profile_lang', 'pl', 'pl.id_profile = p.id_profile AND pl.id_lang = ' . (int) $idLang);
        $sql->where('p.id_profile IN (' . implode(',', array_map('intval', $ids)) . ')');

        $results = Db::getInstance()->executeS($sql);

        if (!$results) {
            return [];
        }

        $items = [];
        foreach ($results as $row) {
            $items[(int) $row['id_profile']] = [
                'id' => (int) $row['id_profile'],
                'name' => $row['name'],
            ];
        }

        $ordered = [];
        foreach ($ids as $id) {
            if (isset($items[(int) $id])) {
                $ordered[] = $items[(int) $id];
            }
        }

        return $ordered;
    }

    // =========================================================================
    // ORDER STATES
    // =========================================================================

    /**
     * Preview order state conditions
     */
    public function previewOrderStateConditions($groups, $limit, $idLang, $idShop, $offset = 0)
    {
        $allIds = $this->queryHandler->getMatchingOrderStateIds($groups, $idLang, $idShop);

        if (empty($allIds)) {
            return $this->buildPreviewResponse([], [], $limit, $offset);
        }

        $pageIds = array_slice($allIds, (int) $offset, (int) $limit);
        $items = $this->getOrderStatePreviewData($pageIds, $idLang);

        return $this->buildPreviewResponse($allIds, $items, $limit, $offset);
    }

    /**
     * Get order state preview data
     */
    public function getOrderStatePreviewData(array $ids, $idLang)
    {
        if (empty($ids)) {
            return [];
        }

        $sql = new DbQuery();
        $sql->select('os.id_order_state, osl.name, os.color, os.paid, os.shipped, os.delivery, os.invoice');
        $sql->from('order_state', 'os');
        $sql->leftJoin('order_state_lang', 'osl', 'osl.id_order_state = os.id_order_state AND osl.id_lang = ' . (int) $idLang);
        $sql->where('os.id_order_state IN (' . implode(',', array_map('intval', $ids)) . ')');

        $results = Db::getInstance()->executeS($sql);

        if (!$results) {
            return [];
        }

        $items = [];
        foreach ($results as $row) {
            $items[(int) $row['id_order_state']] = [
                'id' => (int) $row['id_order_state'],
                'name' => $row['name'],
                'color' => $row['color'],
                'paid' => (bool) $row['paid'],
                'shipped' => (bool) $row['shipped'],
                'delivery' => (bool) $row['delivery'],
                'invoice' => (bool) $row['invoice'],
            ];
        }

        $ordered = [];
        foreach ($ids as $id) {
            if (isset($items[(int) $id])) {
                $ordered[] = $items[(int) $id];
            }
        }

        return $ordered;
    }

    // =========================================================================
    // TAXES
    // =========================================================================

    /**
     * Preview tax conditions
     */
    public function previewTaxConditions($groups, $limit, $idLang, $idShop, $offset = 0)
    {
        $allIds = $this->queryHandler->getMatchingTaxIds($groups, $idLang, $idShop);

        if (empty($allIds)) {
            return $this->buildPreviewResponse([], [], $limit, $offset);
        }

        $pageIds = array_slice($allIds, (int) $offset, (int) $limit);
        $items = $this->getTaxPreviewData($pageIds, $idLang);

        return $this->buildPreviewResponse($allIds, $items, $limit, $offset);
    }

    /**
     * Get tax preview data
     */
    public function getTaxPreviewData(array $ids, $idLang)
    {
        if (empty($ids)) {
            return [];
        }

        $sql = new DbQuery();
        $sql->select('t.id_tax, tl.name, t.rate, t.active');
        $sql->from('tax', 't');
        $sql->leftJoin('tax_lang', 'tl', 'tl.id_tax = t.id_tax AND tl.id_lang = ' . (int) $idLang);
        $sql->where('t.id_tax IN (' . implode(',', array_map('intval', $ids)) . ')');

        $results = Db::getInstance()->executeS($sql);

        if (!$results) {
            return [];
        }

        $items = [];
        foreach ($results as $row) {
            $items[(int) $row['id_tax']] = [
                'id' => (int) $row['id_tax'],
                'name' => $row['name'],
                'rate' => (float) $row['rate'],
                'active' => (bool) $row['active'],
            ];
        }

        $ordered = [];
        foreach ($ids as $id) {
            if (isset($items[(int) $id])) {
                $ordered[] = $items[(int) $id];
            }
        }

        return $ordered;
    }

    /**
     * Get combination preview data for product attribute combinations
     *
     * @param array $combinations Array of ['id_product' => X, 'id_product_attribute' => Y]
     * @param int $idLang Language ID
     * @param int $idShop Shop ID
     * @return array Preview data with combination attributes
     */
    public function getCombinationPreviewData(array $combinations, $idLang, $idShop)
    {
        if (empty($combinations)) {
            return [];
        }

        // Collect unique product IDs
        $productIds = array_unique(array_column($combinations, 'id_product'));
        $productAttributeIds = array_column($combinations, 'id_product_attribute');

        if (empty($productIds)) {
            return [];
        }

        // Get product base info
        $productResults = Db::getInstance()->executeS('
            SELECT p.id_product, pl.name, p.reference,
                   (SELECT id_image FROM ' . _DB_PREFIX_ . 'image i WHERE i.id_product = p.id_product AND i.cover = 1 LIMIT 1) as id_image
            FROM ' . _DB_PREFIX_ . 'product p
            INNER JOIN ' . _DB_PREFIX_ . 'product_lang pl ON p.id_product = pl.id_product AND pl.id_lang = ' . (int) $idLang . ' AND pl.id_shop = ' . (int) $idShop . '
            WHERE p.id_product IN (' . implode(',', array_map('intval', $productIds)) . ')
        ');

        $productData = [];
        foreach ($productResults ?: [] as $row) {
            $productData[$row['id_product']] = $row;
        }

        // Get attribute names for each combination
        $attributeResults = Db::getInstance()->executeS('
            SELECT pac.id_product_attribute, GROUP_CONCAT(CONCAT(agl.name, ": ", al.name) ORDER BY agl.name SEPARATOR ", ") as attributes
            FROM ' . _DB_PREFIX_ . 'product_attribute_combination pac
            INNER JOIN ' . _DB_PREFIX_ . 'attribute a ON a.id_attribute = pac.id_attribute
            INNER JOIN ' . _DB_PREFIX_ . 'attribute_lang al ON al.id_attribute = a.id_attribute AND al.id_lang = ' . (int) $idLang . '
            INNER JOIN ' . _DB_PREFIX_ . 'attribute_group_lang agl ON agl.id_attribute_group = a.id_attribute_group AND agl.id_lang = ' . (int) $idLang . '
            WHERE pac.id_product_attribute IN (' . implode(',', array_map('intval', $productAttributeIds)) . ')
            GROUP BY pac.id_product_attribute
        ');

        $attributeData = [];
        foreach ($attributeResults ?: [] as $row) {
            $attributeData[$row['id_product_attribute']] = $row['attributes'];
        }

        // Build preview items
        $items = [];
        $context = Context::getContext();
        foreach ($combinations as $combo) {
            $productId = (int) $combo['id_product'];
            $attributeId = (int) $combo['id_product_attribute'];

            if (!isset($productData[$productId])) {
                continue;
            }

            $product = $productData[$productId];
            $imageUrl = '';
            if ($product['id_image']) {
                $imageUrl = $context->link->getImageLink($product['name'], $product['id_image'], 'small_default');
            }

            $items[] = [
                'id' => $productId,
                'id_product_attribute' => $attributeId,
                'name' => $product['name'],
                'reference' => $product['reference'],
                'image' => $imageUrl,
                'attributes' => $attributeData[$attributeId] ?? '',
                'isCombination' => true,
            ];
        }

        return $items;
    }
}
