<?php

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

use PrestaShop\Module\LCVPrestaTheme\Classes\AutoUpdater;
use PrestaShop\Module\LCVPrestaTheme\Config\ConfigTheme;
use PrestaShop\Module\LCVPrestaTheme\Installer\Install;
use PrestaShop\PrestaShop\Adapter\SymfonyContainer;
use PrestaShop\Module\LCVPrestaTheme\Classes\ThemeTools;
use PrestaShop\PrestaShop\Adapter\Presenter\Product\ProductListingPresenter;
use PrestaShop\PrestaShop\Adapter\Image\ImageRetriever;
use PrestaShop\PrestaShop\Adapter\Product\PriceFormatter;
use PrestaShop\PrestaShop\Adapter\Product\ProductColorsRetriever;

class LCVPrestaTheme extends Module
{    
    /**
     * Numéro de version du module
     * 
     * @var string
     */
    public const MODULE_VERSION = '1.5.9';

    /**
     * Version installée
     */
    public string $installed_version = "";

    /**
     * Préfixe général du module utilisé pour stocker des informations dans les différentes tables de prestashop sans conflit de nommage
     * 
     * @var string
     */
    public const MODULE_PREFIX = 'pt_';

    /**
     * La page actuelle est en cache
     * 
     * @var boolean
     */
    private $_PageCached = false;

    /**
     * Configuration du cache
     */
    private $_CacheCfg = null;

     /**
     * Configuration du thème
     */
    private $_Config = null;

    /**
     * Cache de langue
     */
    private $_LangCache = [];


     /**
     * Instanciation du module
     */
    public function __construct()
    {        
        $this->name = strtolower(get_class($this)); // Doit correspondre au nom du module, dans notre cas : monmodule
        $this->tab = 'front_office_features'; // Correspond à l'onglet de catégorisation du module, pour tous les connaitre : https://devdocs.prestashop.com/1.7/modules/creation/
        $this->author = 'LCV'; // L'auteur
        $this->version = self::MODULE_VERSION;              // Version disponible du module
        $this->installed_version = self::MODULE_VERSION;    // Version actuelle du module
        $this->ps_versions_compliancy = [ // Permet de limiter les versions compatibles
            'min' => '8.0',
            'max' => _PS_VERSION_
        ];
        $this->bootstrap = true;

        parent::__construct();

        $this->displayName = $this->l('LCVPrestaTheme'); // Nom d'affichage
        $this->description = $this->l('Fourniture et gestion d\'un thème universel adaptable pour les clients de LCV.'); // Description du module
        $this->confirmUninstall = $this->l('Êtes-vous sûr de vouloir désinstaller ce module ?');     

        // On déclenche les mises à jour nécessaires
        $currentlyInstalledVersion = Configuration::get(self::MODULE_PREFIX . "VERSION");
        if (!$currentlyInstalledVersion || version_compare($currentlyInstalledVersion, self::MODULE_VERSION) < 0 || isset($_REQUEST["force_update"]))
        {
            $this->update();
            Configuration::updateValue(self::MODULE_PREFIX . "VERSION", self::MODULE_VERSION);
        }

        // Chargement de la configuration du cache
        $this->_CacheCfg = $this->loadCacheCfg();
    }

    /**
     * Charge la configuration du cache
     */
    public function loadCacheCfg() : array
    {
        $cacheCfg = [
            "ENABLED" => false,
            "INVALID_PRODUCT_CACHE" => true,
            "CACHE_MAX_AGE" => 3600,
        ];

        $savedCfg = Configuration::get(self::MODULE_PREFIX . "CACHE_CFG");
        if ($savedCfg)
        {
            $savedCfg = json_decode($savedCfg, true);
            if ($savedCfg)
            {
                // Pour chacun des propriétés du cache, on regarde si elle existe dans la sauvegarde
                foreach (array_keys($cacheCfg) as $key)
                {
                    if (isset($savedCfg[$key]))
                        $cacheCfg[$key] = $savedCfg[$key];
                }                
            }
        }

        return $cacheCfg;
    }

    /**
     * Retourne la configuration du cache chargée !
     */
    public function getCacheCfg() : array
    {
        return $this->_CacheCfg;
    }
    
    /**
     * Installation du module
     * 
     * @return bool
     */
    public function install(): bool
    {
        // Ajout de l'autoload car il n'existe pas encore à cet endroit !
        require_once _PS_MODULE_DIR_ . $this->name . '/vendor/autoload.php';

        return (parent::install()
            && Install::installTheme($this)
            && Install::installEmployeeProfile()
            && Install::hidePrestaShopBackOfficeIdentity($this)
            && Install::installTab($this)
            && Install::deleteModule()
            && Install::registerHooks($this)
            && Install::wishlistDb($this)
            && Install::setPerformanceDefaults()
            && Install::installClickCollectOrderState($this)
        );
    }

    /**
     * update
     * Mise à jour de l'environnement du module :
     *  - base de données
     *  - hooks
     *  - thème ...
     *
     * @return bool vrai si pas de soucis, faux si erreurs
     */
    public function update(): bool
    {
        // Ajout de l'autoload car il n'existe pas encore à cet endroit !
        require_once _PS_MODULE_DIR_ . $this->name . '/vendor/autoload.php';

        if (!Install::registerHooks($this))
            return false;

        // Mise à jour du module => on redéploie AUSSI les fichiers du thème
        // (assets/meridian -> themes/meridian) pour que le thème suive le module.
        Install::installTheme($this);

        // Statut de commande « Click & Collect » (idempotent).
        Install::installClickCollectOrderState($this);

        // installTheme vient d'écraser var.css / custom.css par les valeurs par
        // défaut du dépôt : on les régénère depuis la configuration back-office
        // pour ne pas perdre les personnalisations (couleurs, polices, CSS custom).
        try {
            ThemeTools::updateCssFile();
            $config = ConfigTheme::load();
            if (!empty($config->configCss[0]) && method_exists($config->configCss[0], 'copyCss')) {
                $config->configCss[0]->copyCss();
            }
        } catch (\Throwable $e) {
            \PrestaShopLogger::addLog('LCVPrestaTheme : régénération du CSS personnalisé après mise à jour impossible : ' . $e->getMessage(), 2);
        }

        return true;
    }


    /**
    * Désinstallation du module
    */
    public function uninstall(): bool
    {
        return (parent::uninstall()
            && Install::restorePrestaShopBackOfficeIdentity()
            && Install::uninstallTab()
            && Install::uninstallTheme()
        );
    }


    /**
     * hookDisplayBackOfficeHeader
     * 
     * Ajoute le CSS du module uniquement dans la pages de notre module
     * 
     */
    public function hookDisplayBackOfficeHeader(){

        // Récupérer l'URL actuelle
        $current_url = $_SERVER['REQUEST_URI'];
        

        // Vérifier si le module est présent dans l'URL
        if (str_contains($current_url, $this->name)) {
            // Ajouter le CSS seulement sur les pages du module
            $this->context->controller->addCSS($this->_path . 'views/css/adminPresta_theme.css');
            $this->context->controller->addCSS(_PS_BO_ALL_THEMES_DIR_ . 'new-theme/js/components/form/choice-tree.js');
            $this->context->controller->addJS($this->_path . 'views/js/admin.js');
       }

       // Toutes les 24h, on regarde s'il n'y a pas une mise à jour du module
        $lastCheck = \Configuration::get($this->name . '_last_update_check');
        if (!$lastCheck || (time() - $lastCheck) > 86400)
        {                
            // On enregistre la date de la dernière vérification
            \Configuration::updateValue($this->name . '_last_update_check', time());

            $updater = new AutoUpdater($this);
            $updater->searchUpdate();
        }
        
    }
    
    /*
    * Page de configuration
    */
    public function getContent()
    {
        Tools::redirectAdmin(
            SymfonyContainer::getInstance()->get('router')->generate($this->name . '_changelog')
        );
    }

    /**
     * getConfig
     * 
     * Retourne la configuration du module
     * 
     * @return ConfigTheme
     */
    public function getConfig()
    {        
        if (!$this->_Config)
            $this->_Config = ConfigTheme::load();

        if (!$this->_Config)
            throw new \Exception('Impossible de charger la configuration du module LCVPrestaTheme');

        return $this->_Config;
    }

    /**
     * Renvoi vrai si la page actuelle devrait être mise en cache
     */
    public function isPageCached()
    {
        return $this->_PageCached;
    }


    
   public function hookDisplayHome()
    {
        
        $config = $this->getConfig();
        $blocksData = [];

        $orderedBlocks = $config->configBlockHome ?? [];

        foreach ($orderedBlocks as $block) {

            switch ($block['type']) {

                /* =============================
                * SLIDER
                * ============================= */
                case 'slider_group':

                    $param = $config->configSliderGroup[$block['ref']] ?? null;

                    if (!$param || !$param->isVisible || $param->width ) {
                        break;
                    }
                  

                    $blocksData[] = [
                        'type'   => 'slider_group',
                        'slider' => $param,
                        'slides' => $param->items ?? [],
                    ];

                break;


                /* =============================
                * CATEGORIES
                * ============================= */
                case 'category_group':

                    $param = $config->configCategoryGroup[$block['ref']] ?? null;

                    if (!$param || !$param->isVisible) {
                        break;
                    }

                    

                    $blocksData[] = [
                        'type'       => 'category_group',
                        'group'      => $param,
                        'categories' => $param->items ?? [],
                    ];

                break;


                
                /* =============================
                * PRODUIT GROUP
                * ============================= */


                case 'product_group':

                    $param = $config->configProductHomeGroup[$block['ref']] ?? null;

                    if (!$param || !$param->isVisible) {
                        break;
                    }

                    // 👇 Les données sont dans items[0] (singleItemMode)
                    $item = $param->items[0] ?? null;

                    

                    if (!$item) {
                        break;
                    }

                    $products = [];

                    switch ($item['typeProduit']) {

                    
                        case 'new':
                            $raw = Product::getNewProducts(
                                (int)$this->context->language->id,
                                1,
                                $item['nbProduit']
                            );
                            $products = $this->hydrateProducts($raw);
                        break;

                        case 'sale':
                            $raw = Product::getPricesDrop(
                                (int)$this->context->language->id,
                                1,
                                $item['nbProduit']
                            );
                            $products = $this->hydrateProducts($raw);
                        break;

                        case 'custom':
                            
                            $ids = !empty($item['productIds'])
                                ? array_filter(array_map('intval', explode(',', $item['productIds'])))
                                : [];

                            $raw = [];
                            foreach ($ids as $idProduct) {
                                $p = new Product(
                                    $idProduct,
                                    false,
                                    (int)$this->context->language->id
                                );
                                if (!$p->id || !$p->active) {
                                    continue;
                                }
                                $raw[] = [
                                    'id_product'          => $p->id,
                                    'link_rewrite'        => is_array($p->link_rewrite)
                                                                ? $p->link_rewrite[(int)$this->context->language->id]
                                                                : $p->link_rewrite,
                                    'id_category_default' => $p->id_category_default,
                                    'name'                => is_array($p->name)
                                                                ? $p->name[(int)$this->context->language->id]
                                                                : $p->name,
                                ];
                            }
                            $products = $this->hydrateProducts($raw);
                        break;
                    }

                    $blocksData[] = [
                        'type'     => 'product_group',
                        'param'    => $param,
                        'item'     => $item,      // 👈 title, typeProduit etc. accessibles dans Twig
                        'products' => $products,
                    ];

                break;


                
                /* =============================
                * Marque
                * ============================= */
                case 'brand':

                    $param = $config->configBrand[$block['ref']] ?? null;
                    

                    if (!$param || !$param->isVisible) {
                        break;
                    }
                    

                    $blocksData[] = [
                        'type'     => 'brand',
                        'brands' => $param ?? [],
                    ];
                    
                break;

                /* =============================
                * Reassurance
                * ============================= */

                case 'Reassurance_group':

                    $param = $config->configReassuranceGroup[$block['ref']] ?? null;

                    if (!$param || !$param->isVisible || $param->hook != 'home') {
                        break;
                    }


                    $blocksData[] = [
                        'type'       => 'reassurance_group',
                        'group'      => $param,
                        'reassurances' => $param->items ?? [],
                    ];

                break;


                /* =============================
                * About
                * ============================= */

                case 'About_group':

                    $param = $config->configAboutGroup[$block['ref']] ?? null;

                    if (!$param || !$param->isVisible) {
                        break;
                    }


                    $blocksData[] = [
                        'type'       => 'about_group',
                        'group'      => $param,
                        'about' => $param->items ?? [],
                    ];

                break;
            }
        }

        $this->context->smarty->assign([
            'orderedBlocks' => $blocksData
        ]);

        return $this->display(__FILE__, '/views/templates/hook/home.tpl');
    }


    /* ======================================================
     * HYDRATE PRODUITS (COMMUN PROMO / NOUVEAUX)
     * ====================================================== */
    private function hydrateProducts($products)
    {
        if (empty($products)) {
            return [];
        }

        $assembler = new ProductAssembler($this->context);
        $presenter  = new ProductListingPresenter(
            new ImageRetriever($this->context->link),
            $this->context->link,
            new PriceFormatter(),
            new ProductColorsRetriever(),
            $this->context->getTranslator()
        );

        $presenterFactory = new \ProductPresenterFactory($this->context);
        $presentationSettings = $presenterFactory->getPresentationSettings();

        $presented = [];
        foreach ($products as $rawProduct) {
            $assembled = $assembler->assembleProduct($rawProduct);
            $presented[] = $presenter->present(
                $presentationSettings,
                $assembled,
                $this->context->language
            );
        }

        return $presented;
    }

    /**
     * hookDisplayFooterBefore
     * 
     * Affiche le contenu reassurance avant le footer
     * 
     * @return string
     */
    public function hookDisplayFooterBefore(){
        $config = $this->getConfig();
         
        foreach($config->configReassuranceGroup as $reassurance){
             
            if($reassurance->hook === 'footerbefore'){
               
                $this->context->smarty->assign([
                    'reassurances' => $reassurance->items
                ]);

                return $this->display(__FILE__, '/views/templates/hook/reassurance.tpl');
            }

        }

        
    }


    /**
     * hookDisplayHomeSliderFullWidth
     * 
     * Affiche le slider en pleine largeur
     * 
     * @return string
     */
    public function hookDisplayHomeSliderFullWidth(){
         $config = $this->getConfig();

        foreach ($config->configSliderGroup as $slider) {

            if (!$slider->width || !$slider->isVisible) {
                continue;
            }

            $this->context->smarty->assign([
                'slider' => $slider,
                'slides' => $slider->items ?? []
            ]);


             return $this->display(__FILE__, '/views/templates/hook/slider.tpl');
            
        }
    }


    /**
     * hookDisplayDeliveryEstimation
     * 
     * Affiche l'estimation de livraison sur la page produit 
     * 
     * @return string
     */
    public function hookDisplayDeliveryEstimation()
    {
       
        $data =ThemeTools::getShippingEstimator();
        

         $this->context->smarty->assign([
            'delivery' => $data['delivery'], // Estimated delivery date
            'shippingTime' => $data['shippingTime'], // Shipping cutoff time
        ]);
        
        return $this->display(__FILE__, '/views/templates/hook/delivery_estimation.tpl');
        
    }

    public function hookDisplayShippingEstimatorCheckout(array $params)
    {
        
        $carrierId = $params['carrier']; // Get the selected carrier ID
        $data = ThemeTools::getShippingEstimator();

        
        // Assign variables to Smarty template
        $this->context->smarty->assign([
            // Le transporteur peut ne pas avoir de délai configuré (clé absente) :
            // on retombe sur null → le template ({if $delivery != 0}) n'affiche rien.
            'delivery' => $data['deliverys'][$carrierId] ?? null, // Delivery for this carrier
            'shippingTime' => $data['shippingTime'],      // Shipping cutoff
        ]);

        // Display the checkout template
        return $this->display(__FILE__, '/views/templates/hook/shippingestimatorcheckout.tpl');
    }


    /**
     * hookDisplayFreeDelivery
     * 
     * Affiche le message de livraison gratuite en fonction du montant dans le panier du client 
     * 
     * @return string
     */
    public function hookDisplayFreeDelivery(){
        $config = $this->getConfig();

        $livraison = $config->configLivraison[0];
        
        $cart = $this->context->cart;
        $minimumAmout =  $livraison->freeDelivery;
        $cartTotal = $cart->getOrderTotal(true, 4);
        $amount_left = $minimumAmout- $cartTotal;
        $this->context->smarty->assign([
            'amount_left' => $amount_left,
            'free'=>$minimumAmout
        ]);
        return $this->display(__FILE__, '/views/templates/hook/free-delivery.tpl');
    }


    /**
     * hookDisplaySociaux
     * 
     * Affiche les icones de réseaux sociaux
     * 
     * @return string
     */
    public function hookDisplaySociaux(){
        $config = $this->getConfig();

        if (count($config->configResaux) > 0)
        {
            $this->context->smarty->assign([
                'sociaux' => $config->configResaux,
            ]);

            return $this->display(__FILE__, '/views/templates/hook/sociaux.tpl');
        }
        else
            return '';
    }

    /**
     * hookDisplayFilter
     * 
     * degreffe le module ps_facetedsearch du hook displayLeftColumn (hook de base) et le greffe sur le hook displayFilter (hook personnalisé)
     * 
     * @return string
     */
    public function hookDisplayFilter(){

        $module = Module::getInstanceByName('ps_facetedsearch');
        if (Validate::isLoadedObject($module)) {
            // Dégreffer le module du hook displayLeftColumn
            $module->unregisterHook('displayLeftColumn');
        
            // Greffer le module sur ton hook personnalisé
            $module->registerHook('displayFilter');
        }
        else
            throw new Exception('Le module ps_facetedsearch n\'est pas installé');
    }


    /**
     * hookActionFacetedSearchFilters
     *
     * Restreint le filtrage à facettes aux produits ayant une DÉCLINAISON EN STOCK
     * pour la/les valeur(s) d'attribut sélectionnée(s).
     *
     * Pourquoi : ps_facetedsearch filtre sur l'EXISTENCE d'une déclinaison portant
     * l'attribut (son index `layered_product_attribute` ne stocke que id_product +
     * id_attribute, ni la déclinaison exacte ni le stock). Conséquence : une taille
     * dont la déclinaison existe mais est à 0 remonte quand même. On corrige en
     * calculant, PAR groupe d'attributs sélectionné, l'ensemble des produits ayant
     * une déclinaison en stock (>0) pour l'une des valeurs choisies (OR intra-groupe),
     * puis en intersectant les groupes entre eux (AND inter-groupes).
     *
     * Le hook est déclenché dans Search::addControllerSpecificFilters(), donc AVANT
     * useFiltersAsInitialPopulation() : le filtre id_product ajouté ici entre bien
     * dans la population initiale de la requête (produits + comptes de facettes).
     *
     * NB : ne cible que les attributs (with_attributes_*), pas les caractéristiques
     * (with_features_*) qui n'ont pas de stock. Sans gestion de stock activée, aucun
     * filtrage n'est appliqué.
     */
    public function hookActionFacetedSearchFilters(array $params)
    {
        if (!Configuration::get('PS_STOCK_MANAGEMENT')) {
            return;
        }

        $search = $params['search'] ?? null;
        if (!is_object($search) || !method_exists($search, 'getSearchAdapter')) {
            return;
        }
        $adapter = $search->getSearchAdapter();

        // Groupes d'attributs sélectionnés : opérations 'with_attributes_<idGroupe>'
        // de structure [[['id_attribute', [idAttr, ...]]]].
        $attributeGroups = [];
        foreach ($adapter->getOperationsFilters() as $name => $operations) {
            if (strpos((string) $name, 'with_attributes_') !== 0) {
                continue;
            }
            $ids = [];
            foreach ($operations as $orGroup) {
                foreach ((array) $orGroup as $condition) {
                    if (is_array($condition) && isset($condition[0], $condition[1])
                        && $condition[0] === 'id_attribute' && is_array($condition[1])) {
                        foreach ($condition[1] as $attrId) {
                            $ids[(int) $attrId] = (int) $attrId;
                        }
                    }
                }
            }
            if (!empty($ids)) {
                $attributeGroups[] = array_values($ids);
            }
        }

        if (empty($attributeGroups)) {
            return; // aucun filtre d'attribut actif : rien à restreindre
        }

        // Intersection (AND) entre groupes des produits ayant une déclinaison en stock.
        $pool = null;
        foreach ($attributeGroups as $attributeIds) {
            $products = $this->getInStockProductsForAttributes($attributeIds);
            $pool = ($pool === null) ? $products : array_intersect($pool, $products);
            if (empty($pool)) {
                break;
            }
        }

        // Pool vide => aucun résultat (même convention que le backport recherche du module).
        $adapter->addFilter('id_product', empty($pool) ? ['NULL'] : array_values($pool));
    }

    /**
     * Produits ayant au moins une déclinaison EN STOCK (>0) portant l'une des
     * valeurs d'attribut données (OR). Respecte la restriction boutique du stock.
     *
     * @param int[] $attributeIds
     *
     * @return int[]
     */
    private function getInStockProductsForAttributes(array $attributeIds): array
    {
        $attributeIds = array_filter(array_map('intval', $attributeIds));
        if (empty($attributeIds)) {
            return [];
        }

        $stockShopRestriction = StockAvailable::addSqlShopRestriction(null, null, 'sa');

        $sql = new DbQuery();
        $sql->select('pa.id_product');
        $sql->from('product_attribute_combination', 'pac');
        $sql->innerJoin('product_attribute', 'pa', 'pa.id_product_attribute = pac.id_product_attribute');
        $sql->innerJoin('stock_available', 'sa', 'sa.id_product_attribute = pac.id_product_attribute' . $stockShopRestriction);
        $sql->where('pac.id_attribute IN (' . implode(',', $attributeIds) . ')');
        $sql->where('sa.quantity > 0');
        $sql->groupBy('pa.id_product');

        $rows = Db::getInstance()->executeS($sql);
        if (!is_array($rows)) {
            return [];
        }

        $products = [];
        foreach ($rows as $row) {
            $products[] = (int) $row['id_product'];
        }

        return $products;
    }


    /**
     * hookDisplayHeader
     * 
     * Permet de passer des variables globale qui sont accessible dans le front end sur n'importe quelle page 
     * 
     */
    public function hookDisplayHeader(){
        $config = $this->getConfig();

        $base_url = \Context::getContext()->shop->getBaseURL();

        // recuperer l'ID du gtag dans la configuration            
        $gtag_id = $config->configTracking[0]->gtag ?? null;
        $facebookPixelId = $config->configTracking[0]->fbPixel ?? null;
        // si l'ID du gtag est défini, on l'ajoute dans les variables globales js
        if ($gtag_id) {
                Media::addJsDef([
                    'gtag_id' => $gtag_id,
                    'fb_id' => $facebookPixelId,
                ]);
        }

        $this->context->controller->registerJavascript(
            'module-prestatheme-wishlist-btn', // identifiant unique
            'modules/' . $this->name . '/views/js/wishlist-btn.js',
            [
                'position' => 'bottom',   // ou 'head' si besoin
                'priority' => 150,        // >100 = après jQuery
            ]
        );

        $this->context->controller->registerJavascript(
            'module-prestatheme-estimation', // identifiant unique
            'modules/' . $this->name . '/views/js/estimation.js',
            [
                'position' => 'bottom',   // ou 'head' si besoin
                'priority' => 150,        // >100 = après jQuery
            ]
        );
            
        // Assigner les variables globales
        $this->context->smarty->assign([
            'catalogue' => $config->configCatalogue,
            'sliderParameter' => $config->configSliderGroup,
            'productParameter'=> $config->configProduct,
            'headerParameter' => $config->configHeader,
            'bandeauParameter'=> $config->configTopBandeauParameter,
            'bandeaux' => $config->configTopBandeau,
            'base_url' => $base_url,
        ]);
    }


    /**
     * hookDisplayBandeau
     * 
     * Affiche le bandeau en haut de la page
     * 
     * @return string
     */
    public function hookDisplayBandeau(){
        $config = $this->getConfig();

        if (isset($config))
        {
            $this->context->smarty->assign([
                'bandeaux' => $config->configTopBandeau,
            ]);


            return $this->display(__FILE__, '/views/templates/hook/bandeau.tpl');
        }
        else
            return ''; // Si la configuration du bandeau est vide, ne rien afficher
    }

    /**
     * hookDisplayBandeau
     * 
     * Affiche le pop up de cookie
     * 
     * @return string
     */
    public function hookDisplayCookie()
    {
        $this->context->controller->addJS($this->_path . 'views/js/cookie.js');
        return $this->display(__FILE__, '/views/templates/hook/cookie.tpl');
    }


    /**
     * hookDisplayPopup
     * 
     * Affiche le pop up newsletter
     * 
     * @return string
     */
    public function hookDisplayPopup(){
        $config = $this->getConfig();
        $this->context->smarty->assign([
                'popup' => $config->configPopUp,
            ]);
        return $this->display(__FILE__, '/views/templates/hook/popup.tpl');
    }

    public function hookDisplayMagasin(){
        $config = $this->getConfig();

        if (isset($config))
        {
            $this->context->smarty->assign([
                'stores' => $config->configStore,
            ]);

            return $this->display(__FILE__, '/views/templates/hook/store.tpl');
        }
        else
            return ''; // Si la configuration du magasin est vide, ne rien afficher
    }

    public function hookDisplayPopupStore(){
        $config = $this->getConfig();
        if (isset($config))
        {
            $this->context->smarty->assign([
                'stores' => $config->configStore,
            ]);

            return $this->display(__FILE__, '/views/templates/hook/popup_store.tpl');
        }
        else
            return ''; // Si la configuration du magasin est vide, ne rien afficher
    }

    public function hookDisplayNav2(){
        
        $this->context->smarty->assign(array(
            'link' => $this->context->link->getModuleLink($this->name, 'wishlist', array(), true)
        ));
        return $this->display(__FILE__, '/views/templates/hook/nav-wishlist.tpl');
    }

    public function hookDisplayWishlistButton($params){
        if (!isset($params['product'])) {
            return '';
        }

        $product = $params['product'];
        $idCustomer = (int)$this->context->customer->id;

        // Vérifie si le produit est déjà dans la wishlist
        $isInWishlist = false;
        if ($idCustomer && isset($product['id_product'])) {
            $isInWishlist = (bool)Db::getInstance()->getValue('
                SELECT COUNT(*) 
                FROM `'._DB_PREFIX_.'wishlist`
                WHERE id_customer = '.(int)$idCustomer.' 
                AND id_product = '.(int)$product['id_product']
            );
        }

        $this->context->smarty->assign([
            'product' => $product,
            'is_in_wishlist' => $isInWishlist,
        ]);

        return $this->display(__FILE__, 'views/templates/hook/wishlist-button.tpl');
    }

    
    public function hookDisplayCustomerAccount()
    {
        $this->context->smarty->assign(array(
            'link' => $this->context->link->getModuleLink('prestatheme', 'wishlist', array(), true)
        ));

        return $this->display(__FILE__, 'views/templates/hook/account-wishlist.tpl');
    }



    /**
     * hookDisplayTop
     * 
     * Affiche le menu en haut de la page
     * 
     * @return string
     */
    public function hookDisplayTop(){
        $config = $this->getConfig();

        if (isset($config))
        {
            $this->context->smarty->assign([
                'menus' => $config->configMenuParent,
            ]);

            // Si un menu de type "brand" est présent, charger les marques A-Z dynamiquement
            $hasBrandMenu = false;
            foreach ($config->configMenuParent as $parent) {
                if (isset($parent->menu['type']) && $parent->menu['type'] === 'brand') {
                    $hasBrandMenu = true;
                    break;
                }
            }
            if ($hasBrandMenu) {
                $id_lang      = $this->context->language->id;
                $manufacturers = \Manufacturer::getManufacturers(false, $id_lang, true);
                usort($manufacturers, fn($a, $b) => strcasecmp($a['name'], $b['name']));

                $brands_by_letter = [];
                foreach ($manufacturers as $m) {
                    $letter = mb_strtoupper(mb_substr($m['name'], 0, 1));
                    $brands_by_letter[$letter][] = [
                        'name' => $m['name'],
                        'url'  => $this->context->link->getManufacturerLink((int) $m['id_manufacturer'], null, $id_lang),
                    ];
                }
                ksort($brands_by_letter);
                $this->context->smarty->assign(['brands_by_letter' => $brands_by_letter]);
            }

            return $this->display(__FILE__, '/views/templates/hook/menu.tpl');
        }
        else
            return ''; // Si la configuration du menu est vide, ne rien afficher
    }


    /**
     * hookDisplayAfterBodyOpeningTag
     * 
     * Affiche le code noscript pour le gtag dans le body
     * 
     * @return string
     */
    public function hookDisplayAfterBodyOpeningTag(){
        $config = $this->getConfig();
        
        //Récupération des IDs de tracking
        $googleTagId = $config->configTracking[0]->gtag ?? null;
        $facebookPixelId = $config->configTracking[0]->fbPixel ?? null;

        //Assignation Smarty uniquement si un ID est présent
        if ($googleTagId || $facebookPixelId) {
            $this->context->smarty->assign([
                'gtag_id' => $googleTagId,
                'fb_id' => $facebookPixelId,
            
            ]);

            return $this->display(__FILE__, '/views/templates/hook/noscript-tracking.tpl');
        }         
    }

    /**
     * Gestion du cache :
     * Juste après avoir rafraîchit le stock, on réinitialise la page produit.
     */
    public function hookActionObjectStockAvailableUpdateAfter($params)
    {
        if ($params["object"]->id_product_attribute == 0)
            $this->resetProductCache(new Product($params["object"]->id_product));
    }

    /**
     * Gestion du cache :
     * une fois avoir construit toute la page, on injecte les données dynamiques pour le cache
     */
    public function hookDisplayBeforeBodyClosingTag($params)
    {
        if (!$this->isPageCached()) {
            echo '<div id="data_cache" data-var="' . $this->buildPopulateDynData() . '"></div>';
        }
        else {
            // Si la page est en cache, on n'affiche rien
            echo '<!-- Cached generated page -->';
        }
    }

    /**
     * Gestion du cache :
     * ajout de la bibliothèque JS permettant d'utiliser le cache
     * 
     */
    public function hookActionFrontControllerSetMedia($params)
    {
        // On every pages
        $this->context->controller->registerJavascript(
            'dyncachejs',
            'modules/'.$this->name.'/assets/dyncache.js',
            [
                'position' => 'foot',
                'inline' => false,
                'priority' => 10,
            ]
        );
    }

    /**
     * Gestion du cache : 
     * Quand un produit est mis à jour, on réinitialise le cache de la page produit
     * 
     */
    public function hookActionObjectProductUpdateAfter($params)
    {
        if ($this->_CacheCfg["ENABLED"])
            $this->resetProductCache($params["object"]);
    }

    /**
     * Gestion du cache : 
     * Selon le contrôleur appelé, on décide si on doit mettre en cache la page ou pas
     * 
     */
    public function hookActionDispatcher($params)
    {
        switch ($params["controller_class"]) {
            case "IndexController":
            case "NewProductsController":
            case "BestSalesController":
            case "PricesDropController":
            case "CategoryController":
            case "CmsController":
            case "ContactController":
            case "SitemapController":
            case "StoresController":
            case "ProductController":
            // Modules safe en GET ...
            case "anbloglistModuleFrontController":
            case "anblogblogModuleFrontController":
                if ($_SERVER["REQUEST_METHOD"] == "GET" || $_SERVER["REQUEST_METHOD"] == "HEAD")
                    $this->_PageCached = true;
                break;

            default:
                // DEBUG : var_dump($params["controller_class"]);
                break;
        }

        if ($this->context?->customer?->isLogged())
        {
            $this->_PageCached = false; // Pas de cache pour les pages des clients connectés

            // Cookie pour signaler qu'on ne doit pas mettre en cache la page en direct, sans passer par les fonctions de Prestashop
            setcookie(
                'PS_LOG_IN',      // nom du cookie
                '1',              // valeur (ex: "1" pour connecté)
                time() + 365 * 24 * 3600,    // durée de vie (1 an)
                '/',              // chemin ("/" pour tout le site)
                null,             // domaine (null = domaine courant)
                false,            // HTTPS uniquement (true si SSL obligatoire)
                true              // accessible uniquement via HTTP (pas JS)
            );
        }
        else
        {
            // Si on a le cookie PS_LOG_IN, on le désactive
            if (isset($_COOKIE['PS_LOG_IN'])) {
                setcookie('PS_LOG_IN', '', time() - 3600, '/');
            }
        }

        // Si on doit mettre la page en cache
        if ($this->_CacheCfg["ENABLED"] && $this->_PageCached) 
        {
            // On n'utilise pas de session pour les pages misent en cache, donc on désactive les sessions
            // de manière élégante = les appels ne font rien et ne retourne rien !
            session_set_save_handler(
                function() { return true; }, // open
                function() { return true; }, // close
                function() { return ''; },   // read
                function() { return true; }, // write
                function() { return true; }, // destroy
                function() { return true; }  // gc
            );

            // Tous les caches générés sont publics, donc accessibles par tous les utilisateurs
            // Mais on envoi '' pour désactiver totalement l'envoi automatique des entêtes de cache pour les gérer nous même
            session_cache_limiter("");  // session_start() ne doit pas produire de header de cache exotique
            session_start();            // On démarre la session pour être sûr de pouvoir overrider le cache                                

            // Et on installe notre préférence :
            // - cache de X secondes
            // - cache public pour le navigateur et les proxy. Ces deux derniers doivent revalider à l'issu du temps imparti
            header(sprintf("Cache-Control: public, max-age=%d, must-revalidate, proxy-revalidate, stale-while-revalidate", $this->_CacheCfg["CACHE_MAX_AGE"] ?? 3600), true);
            
            // Pas de PHPSESSID ni de cookie sur ces pages merci !!
            header_remove("Set-Cookie");

            // On écrit rien !
            $this->context->cookie->disallowWriting();
        }
    }

    /*
    * obj {
        prop1 = "ma propriété 1";
        prop2 = "ma propriété 2";
        prop3 = "ma propriété 3";
    }

    => dynDataExport($obj, ["prop1", "prop3"]);
    ----> [ "prop1" => "ma propriété 1", "prop3" => "ma propriété 3", ]
    */
    private function dynDataExport($obj, $props): array
    {
        $data = [];

        foreach ($props as $prop_name)
            $data[$prop_name] = $obj->$prop_name;

        return $data;
    }

    /**
     * Prépare les données dynamiques à envoyer au navigateur
     */
    private function buildPopulateDynData()
    {
        $data = [
            "customer" => ($this->context && $this->context->customer && $this->context->customer->isLogged()) ? $this->dynDataExport($this->context->customer, ["firstname", "lastname", "email"]) : null,
            "cart" => [
                "products_count" => empty($this->context->cookie->id_cart) ? 0 : Cart::getNbProducts($this->context->cookie->id_cart)
            ]
        ];

        // Initiales du client ?
        if ($data["customer"])
        {
            $initials = "";
            if (isset($data["customer"]["lastname"][0]))
                $initials .= strtoupper($data["customer"]["lastname"][0]);
            if (isset($data["customer"]["firstname"][0]))
                $initials .= strtoupper($data["customer"]["firstname"][0]);            

            $data["customer"]["initials"] = $initials;
        }

        return base64_encode(json_encode($data));
    }

    /**
     * Get langs and cache it
     */
    public function getLangs() : array
    {
        if (!($this->_LangCache ?? false))
            $this->_LangCache = Language::getLanguages(true);

        return $this->_LangCache;
    }
    
    /**
     * Réinitialise le cache pour un produit particulier.
     * La réinitiation du cache se fait en ajoutant /purge-cache/ au début de l'URL du produit et en ajoutant cette URL à
     * un fichier à la racine du site du nom de "purge-cache-urls.txt", 
     * ce fichier sera appelé par un script externe (cron ou manuel) qui va parcourir toutes les URL et forcer la régénération du cache
     *
     * @param Product $id_product
     * @return void
     */
    private function resetProductCache(Product $product)
    {
        if (!$this->_CacheCfg["INVALID_PRODUCT_CACHE"])
            return;

        $pa = [];
        $result = Db::getInstance()->executeS('SELECT id_product_attribute FROM `' . _DB_PREFIX_ . 'product_attribute` WHERE `id_product` = ' . (int) $product->id);
        foreach ($result as $row)
            $pa[] = (int) $row["id_product_attribute"];

        $urls = [];
        foreach ($this->getLangs() as $lang)
        {
            $category = Category::getLinkRewrite($product->id_category_default, (int) $lang["id_lang"]);
            $url = $this->context->link->getProductLink($product->id, 
                                        $product->link_rewrite, 
                                        $category, 
                                        $product->ean13,
                                        (int) $lang["id_lang"],
                                    );

            // Remplacement de la base de l'URL pour ajouter /purge-cache/ au début
            $url = str_replace(_PS_BASE_URL_, _PS_BASE_URL_ . '/purge-cache', $url);
            $urls[$url] = $url;

            foreach ($pa as $ipa)
            {
                $url = $this->context->link->getProductLink($product->id, 
                                        $product->link_rewrite, 
                                        $category, 
                                        $product->ean13,
                                        (int) $lang["id_lang"],
                                        null,
                                        $ipa, 
                                        true,
                                        false,
                                        true
                                    );

                // Remplacement de la base de l'URL pour ajouter /purge-cache/ au début
                $url = str_replace(_PS_BASE_URL_, _PS_BASE_URL_ . '/purge-cache', $url);

                $urls[$url] = $url;
            }
        }

        // Regénération du cache sur les pages trouvées par appel CURL
        $final_urllist = implode(PHP_EOL, $urls);
        file_put_contents(_PS_ROOT_DIR_ . '/var/purge-cache-urls.txt', $final_urllist . PHP_EOL, FILE_APPEND | LOCK_EX);
    }

}