<?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;

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

    /**
     * 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)
        );
    }

    /**
     * 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 true;
        
        return false;
    }


    /**
    * 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;


                /* =============================
                * PRODUITS EN PROMOTION
                * ============================= */
                case 'promotion_produit':

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

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

                    $products = Product::getPricesDrop(
                        (int)$this->context->language->id,
                        1,
                        $param->nbProduit
                    );

                    $blocksData[] = [
                        'type'     => 'promotion_produit',
                        'products' => $this->hydrateProducts($products),
                    ];

                break;


                /* =============================
                * NOUVEAUX PRODUITS
                * ============================= */
                case 'nouveau_produit':

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

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

                    $products = Product::getNewProducts(
                        (int)$this->context->language->id,
                        1,
                        $param->nbProduit
                    );

                    $blocksData[] = [
                        'type'     => 'nouveau_produit',
                        'products' => $this->hydrateProducts($products),
                    ];

                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(?array $products): array
    {
        if (empty($products)) {
            return [];
        }

        $link = new Link();

        foreach ($products as &$product) {

            $productObj = new Product($product['id_product']);

            // image de couverture
            $cover = Product::getCover($product['id_product']);
            $product['id_cover'] = $cover['id_image'] ?? null;

            // toutes les images
            $product['image'] = $productObj->getImages(
                $this->context->language->id
            );

            // prix
            $product['price_ttc'] = $productObj->getPrice(true);
            $product['price_regular']  = $productObj->getPriceWithoutReduct();



            // réduction (%)
            $discount = $product['price_regular'] - $product['price_ttc'];
            $product['discount_percentage'] = $discount > 0
                ? round(($discount / $product['price_regular']) * 100, 2)
                : 0;

            // lien produit
            $product['link'] = $link->getProductLink(
                $product,
                $product['link_rewrite'],
                Category::getLinkRewrite(
                    $product['id_category_default'],
                    $this->context->language->id
                )
            );
        }

        return $products;
    }

    /**
     * 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()
    {
        $config = $this->getConfig();

        if (count($config->configLivraison) > 0)
        {
            $livraison = $config->configLivraison[0];

            $currentTime = time();
            $currentDay = date('w', $currentTime); // Jour actuel (0 = dimanche, 1 = lundi, ..., 6 = samedi)
            $currentHour = date('G', $currentTime); // Heure actuelle (de 0 à 23)
            
            $noShippingDays = $livraison->noShippingDays ?? []; // Jours sans envoi de colis
            
            $deliveryTime = $livraison->deliveryTime; // Délai de livraison en jours
            $shippingTime = $livraison->shippingTime; // Heure limite d'expédition

            // Mapping des jours de la semaine en chiffres (0 = dimanche, 1 = lundi, ..., 6 = samedi)
            $dayMapping = [
                'monday' => 1,
                'tuesday' => 2,
                'wednesday' => 3,
                'thursday' => 4,
                'friday' => 5,
                'saturday' => 6,
                'sunday' => 0,
            ];
            
            // Convertir les jours sans envoi en chiffres
            $noShippingDaysNumeric = array_map(function($day) use ($dayMapping) {
                return $dayMapping[$day];
            }, $noShippingDays);
            
            // Vérifier si l'heure actuelle dépasse l'heure limite d'expédition
            if ($currentHour >= $shippingTime) {
                $currentTime += 86400; // Ajouter 1 jour si c'est trop tard pour expédier le jour même
                $currentDay = date('w', $currentTime); // Jour après avoir ajouté 1 jour
            }
            
            // Vérifier si aujourd'hui est un jour sans expédition
            while (in_array($currentDay, $noShippingDaysNumeric)) {
                $currentTime += 86400; // Ajouter 1 jour supplémentaire si c'est un jour sans expédition
                $currentDay = date('w', $currentTime); // Nouveau jour après ajout
            }
            
            // Calculer la date d'expédition estimée
            $expeditionDate = $currentTime; // C'est maintenant la date d'expédition
            
            // Calculer la date de livraison estimée (en ajoutant le délai de livraison)
            $deliveryDate = $expeditionDate + ($deliveryTime * 86400); // Ajouter le délai de livraison en jours
            
            // Vérifier si la date de livraison tombe un jour sans expédition
            $deliveryDay = date('w', $deliveryDate);
            while (in_array($deliveryDay, $noShippingDaysNumeric)) {
                $deliveryDate += 86400; // Ajouter 1 jour supplémentaire si c'est un jour sans expédition
                $deliveryDay = date('w', $deliveryDate); // Nouveau jour de livraison
            }
            

            $this->context->smarty->assign([
                'expedition' => date('d/m/Y', $expeditionDate), // Date d'expédition,
                'delivery' => date('d/m/Y', $deliveryDate), // Date de livraison
            ]);

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

        return ''; // Si la configuration de livraison est vide, ne rien afficher
    }


    /**
     * 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é');        
    }


    /**
     * 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
            ]
        );
            
        // Assigner les variables globales
        $this->context->smarty->assign([
            'catalogue' => $config->configCatalogue,
            'sliderParameter' => $config->configSliderGroup,
            'productParameter'=> $config->configProduct,
            '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(){
        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,
            ]);

            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);
    }

}