<?php

declare(strict_types=1);

use PrestaShop\Module\LCVPrestaConnector\AutoUpdater;
use PrestaShop\Module\LCVPrestaConnector\BridgeAdapter;
use PrestaShop\Module\LCVPrestaConnector\Controller\VatReportAdminController;
use PrestaShop\Module\LCVPrestaConnector\MultipleProductsManager;
use PrestaShop\Module\LCVPrestaConnector\SupplierRefManager;
use PrestaShop\Module\LCVPrestaConnector\SyncConfiguration;
use PrestaShop\Module\LCVPrestaConnector\Syncer;
use PrestaShop\Module\LCVPrestaConnector\SyncerOrders;
use PrestaShop\PrestaShop\Adapter\SymfonyContainer;
use PrestaShop\Module\LCVPrestaConnector\UrlRedirectManager;

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

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

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

    /**
     * Identifiant du module
     */
    public string $BridgePrefix;

      /**
     * chemin vers tpl
     */
    public string $TplPath;

    /**
     * Préfixe complet des tables
     */
    public string $TblPrefix;

    /**
     * Préfixe court des tables
     */
    public string $TblShortPrefix;

     /**
     * Configuration du module
     *      
     */
    private ?SyncConfiguration $cfg = null;

    /**
     * Lien vers le pont
     */
    private ?BridgeAdapter $bridge = null;

    /**
     * Gestionnaire de produits connexes
     */
    private ?MultipleProductsManager $mpm = null;
    

     /**
     * New instance
     * 
     */
    public function __construct()
    {
        // Nom du module
        $this->name = basename(__FILE__, '.php');

        // On va se noter une constante pour identifier la version de notre module
        // Cette constante, nommée Id est composée des 3 premieres lettres du nom du module
        $this->BridgePrefix = substr($this->name, 0, 3);
        if ($this->BridgePrefix == 'pre')
        {
            // Nous sommes en développement, on va prendre le contenu du fichier local 'DEV'
            // Si ce dernier n'existe pas, on le crée avec 'pol' à l'intérieur
            // Puis on charge le prefixe avec le contenu du fichier (nettoyer des espaces et des retours chariots)
            $file = __DIR__ . '/DEV';
            if (!file_exists($file))
                file_put_contents($file, 'pol');
            $this->BridgePrefix = trim(file_get_contents($file));            
        }

        $this->TblPrefix = _DB_PREFIX_ . 'pc_' . $this->BridgePrefix . '_';
        $this->TblShortPrefix = 'pc_' . $this->BridgePrefix . '_';
        $this->TplPath = '@Modules/' . $this->name . '/views/templates/';

        $this->author = 'LCV';
                
        $this->version = '9.0.23';
        $this->installed_version = '9.0.23';

        $this->tab = 'synchronisation';        
        $this->need_instance = 0;
        $this->ps_versions_compliancy = [
            'min' => '8.1.0',
            'max' => _PS_VERSION_,
        ];
        $this->bootstrap = true;

        parent::__construct();

        $this->displayName = $this->trans('LCVPrestaConnector', [], 'Modules.prestaconnector.Admin');        

        // On enrichi la description avec le changelog ici
        // INJECT_CHANGELOG

        $this->confirmUninstall = $this->trans('Are you sure you want to uninstall this module ?', [], 'Modules.Polarisprestaconnector.Admin');                

        if (isset($_REQUEST["remount_hooks"]))
        {
            $this->registerTabs();
            $this->registerHooks();
        }
    }

    /**
     * L'installation est toujours un succès !
     * 
     */
    public function install()
    {
        // Force l'installation des hooks et des tabs
        if (parent::install())
        {
            $this->registerHooks();
            $this->registerTabs();
        }

        return true;
    }

    /**
     * A la désinstallation
     * CA NE FONCTIONNE PAS !
     */
    /*public function uninstall()
    {
        // On désinstalle
        parent::uninstall();

        // Et on purge les cache
        $this->resetCache();
    }*/

    /**
     * Réinitialisation des caches
     */
    public function resetCache()
    {
        // On va vider le cache de la configuration
        $context = \Context::getContext();
        $context->smarty->clearCompiledTemplate();
        $context->smarty->clearAllCache();

        // 2. Clear filesystem caches
        $cacheDirs = [
            _PS_ROOT_DIR_.'/var/cache/dev',
            _PS_ROOT_DIR_.'/var/cache/prod',
        ];

        $autoUpdater = new AutoUpdater($this);
        foreach ($cacheDirs as $dir)
        {
            if (is_dir($dir)) {
                $autoUpdater->deleteDirectory($dir);
            }
        }
    }

    /**
     * Obtenir les hooks utilisés par ce module
     */
    public function getHooks()
	{
		$hooks = array(
            'displayHeader',
			'dashboardZoneOne',
			'displayBackOfficeHeader',
            'actionGetAdminOrderButtons',

            // Montage hook maisons
            'displayProductPriceBlock',
            'displayProductAdditionalInfo',

            // Hooks maisons
            'displayMultiProductSelector',
            'buildStoreStocks',
            'displayStoreStocks',

            // Controle et action commandes
            'actionValidateOrder',
            'displayAdminLogin',
			);

		return $hooks;
	}

    /**
     * Obtenir les tabs utilisés par ce module
     */
    public function getTabs()
    {
        $backofficeId = $this->getBridge()->getId();
        $tabs = [];

        // Page de mapping
        $name = [];
        foreach (Language::getLanguages() as $lang)
            $name[$lang['id_lang']] = $this->trans('Mapping', [], 'Modules.'.$this->name.'.Admin', $lang['locale'])
                .' '.$this->getBridge()->getId();

        $tabs[] = [ 'class_name' => 'Mappage' . $backofficeId,
                    'route_name' => $this->name . '_map_manufacturer',
                    'parent_class' => 'AdminCatalog',
                    'name' => $name,
                    ];

        // Page d'audit
        $name = [];
        foreach (Language::getLanguages() as $lang)
            $name[$lang['id_lang']] = $this->trans('Audit', [], 'Modules.'.$this->name.'.Admin', $lang['locale'])
                .' '.$this->getBridge()->getId();

        $tabs[] = [ 'class_name' => 'Audit'.$backofficeId,
                    'route_name' => $this->name . '_audit',
                    'parent_class' => 'AdminAdvancedParameters',
                    'name' => $name,
                    ];

        // Relevé de TVA
        $name = [];
        foreach (Language::getLanguages() as $lang)
            $name[$lang['id_lang']] = $this->trans('Relevé de TVA', [], 'Modules.'.$this->name.'.Admin', $lang['locale'])
                .' '.$this->getBridge()->getId();

        $tabs[] = [ 'class_name' => 'VatReport' . $backofficeId,
                    'route_name' => $this->name . '_vat_report',
                    'parent_class' => 'AdminParentOrders',
                    'name' => $name,
                    ];

        return $tabs;
    }    

    /**
     * Réenregistrement des tabs...
     */
    public function registerTabs()
    {
        // Attention, on a pas de container symfony sur un contrôleur v7
        //$container = SymfonyContainer::getInstance();
        //$tabRepository = $container ? $container->get('prestashop.core.admin.tab.repository') : null;
        $tabRepository = null; // Ne fonctionne pas !
        
        foreach ($this->getTabs() as $tabData) {
            // Vérifier si le tab existe déjà
            $exists = $tabRepository ?
                        $tabRepository->findOneIdByClassName($tabData['class_name']) :
                        Tab::getIdFromClassName($tabData['class_name']);
                        
            $tab = $exists ? new Tab((int) $exists) : null;
            if (!$tab || $tab->route_name != $tabData['route_name']) {                
                if (!$tab)
                    $tab = new Tab();
                $tab->active = 1;
                $tab->class_name = $tabData['class_name'];
                $tab->route_name = $tabData['route_name'];
                if (isset($tabData['parent_class']))
                    $tab->id_parent = $tabRepository ?
                                            $tabRepository->findOneIdByClassName($tabData['parent_class']) :
                                            Tab::getIdFromClassName($tabData['parent_class']);                
                $tab->module = $this->name;                
                $tab->name = $tabData['name'];

                // Sauvegarder le tab
                $tab->save();
            }
        }
    }

    /**
     * Déclenche une mise à jour de la base si nécessaire !
     */
    public function dbUpgrade()
    {       
        // Faut-il déclencher la mise à jour de la base de données du module ?
        // pour le savoir on regarde, si la version enregistrée dans la configuration est inférieure à la version actuelle                
        $currentDbVersion = \Configuration::get($this->BridgePrefix . 'prestaconnector_version');
        if (!$currentDbVersion)
            $currentDbVersion = '0.0';

        if (version_compare($currentDbVersion, $this->installed_version) < 0) {        
            // On met à jour la version du module
            require_once _PS_MODULE_DIR_ . $this->name . '/db/upgrade.php';
            $upgrade = new \PrestaShop\Module\LCVPrestaConnector\Upgrade();
            $upgrade->upgradeModule($this, $currentDbVersion);
        }
    }
    
    /*
		Réenregistrement des hooks...
	*/
	public function registerHooks($updatePosition = true)
	{
        // Pour chacun de nos hooks, s'ils ne sont pas déjà enregistrés, on les enregistre
		foreach ($this->getHooks() as $hook)
        {
            if (!$this->isRegisteredInHook($hook))
                $this->registerHook($hook);
        }

		if ($updatePosition)
			$this->updatePosition(Hook::getIdByName('dashboardZoneOne'), 0, 1);
	}

    /**
     * Obtenir l'objet de configuration
     */
    public function getCfg() : SyncConfiguration
    {
        if (!$this->cfg)
            $this->cfg = new SyncConfiguration($this);

        return $this->cfg;
    }

    /**
     * Obtenir le gestionnaire de produits connexes
     */
    public function getMpm() : MultipleProductsManager
    {
        if (!$this->mpm)
            $this->mpm = new MultipleProductsManager($this);

        return $this->mpm;
    }

    /**
     * Obtient le pont configuré
     */
    public function getBridge() : BridgeAdapter
    {
        if (!$this->bridge)
        {
            $classname = "PrestaShop\\Module\\LCVPrestaConnector\\" . $this->BridgePrefix . "\\Bridge";

            // On vérifie que la classe existe, ce qui n'est pas forcément le cas à l'installation du module
            // Si ce n'est pas le cas, on va charger l'autoload nous même
            if (!class_exists($classname))
            {
                if (file_exists(__DIR__ . '/vendor/autoload.php')) {
                    require_once __DIR__ . '/vendor/autoload.php';
                }
                else {
                    throw new \Exception("Impossible de charger le pont " . $classname . " !");
                }
            }

            $this->bridge = new $classname($this);
        }
        return $this->bridge;
    }

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

    /**
     * Dashboard !
     */
    public function hookDashboardZoneOne($params)
	{
        try
        {
            // On contrôle la TVA
            // dernière vérification ?
            $last_check = \Configuration::get('EU_VAT_LAST_CHECKS') ?? '2000-01-01';
            if ($last_check != date('Y-m-d'))
            {
                // On contrôle la TVA
                VatReportAdminController::ControlVat();
                \Configuration::updateValue('EU_VAT_LAST_CHECKS', date('Y-m-d'));
            }

            $this->dbUpgrade();

            $isTesting = preg_match('/^localhost(:\d+)?$/', $_SERVER['HTTP_HOST']);
            $router = SymfonyContainer::getInstance()->get('router');
            $tpl_variables = array(
                'title_name' => 'Backoffice ' . $this->getBridge()->getId(),
                'module_name' => $this->name,
                'module_version' => $this->installed_version,
                'configUrl'=> $router->generate($this->name . "_configuration"),
                'auditUrl' => $router->generate($this->name . "_audit"),
                'logsUrl' => $this->context->link->getAdminLink("AdminLogs"),
                'cronGuardToken' => $this->getCfg()->get(SyncConfiguration::CFG_GUARD_TOKEN),
                'progress' => base64_encode(json_encode(Syncer::getSyncProgress($this)->toArray())),
                'vat_limit' => VatReportAdminController::EU_VAT_LIMIT,
                'vat_alert' => \Configuration::get('EU_VAT_ALERT'),
                // On montre la configuration que si l'utilisateur en cours a une adresse mail qui se finit par @vega-info.fr ou @lcv.fr
                'show_cfg' => $isTesting || preg_match('/@(vega-info|lcvmultimedia)\.fr$/', $this->context->employee->email),                
                'clearCacheUrl'=> $router->generate("admin_clear_cache"),                
            );            

            // Récupère le container Symfony pour ensuite obtenir le service Twig pour le rendu
            $container = \PrestaShop\PrestaShop\Adapter\SymfonyContainer::getInstance();
            /** @var \PrestaShop\Module\LCVPrestaConnector\Service\TwigRenderer $renderer */
            $renderer = $container->get($this->name.'.twig_renderer');

            return $renderer->render($this->TplPath . 'hook/dashboard.html.twig', $tpl_variables);
        }
        catch (\Throwable $ex)
        {
            // Erreur ?
            return '<div class="alert alert-danger">'
                . "Erreur lors du chargement du module LCVPrestaConnector : " . $ex->getMessage()
                . '</div>';
        }
	}

    /**
     * Injectons du CSS
     */
    public function hookDisplayHeader()
    {
        // Ajouter le fichier CSS à la page du front-office
        $this->context->controller->addCSS($this->_path . 'views/css/front.css');

        // Redirection des anciennes URLs si on est sur une page 404
        if ($this->context->controller->php_self === 'pagenotfound') {
            (new UrlRedirectManager($this))->handle();
        }
    }

    /**
     * Injection du JS et CSS dans l'admin
     * 
     */
    public function hookDisplayBackOfficeHeader($params)
    {
        $this->context->controller->addJS($this->_path.'views/js/common.js');
        $this->context->controller->addJS($this->_path.'views/js/admin.js');
        $this->context->controller->addCSS($this->_path.'views/css/admin.css');

        // Si nous sommes sur l'admin controller, alors on tweak l'interface
        switch (Tools::getValue('controller'))
        {
            case "AdminProducts":
                return $this->tweakAdminProductUI($params);
        }

        return '';
    }

    /**
     * Contrôle des champs protégés
     * 
     * @param array $params
     *
     * @return string
     */
    public function tweakAdminProductUI($params) : string
    {
        // Si nous sommes en mode sans synchro de fiche, inutile de protéger les champs, donc on sort
        $cfg = $this->getCfg();
        if ($cfg->get(SyncConfiguration::CFG_WORKMODE) != 1)
            return '';

        $id_supplier = $this->getOrCreateSupplierId();

        $syncCfg = $cfg->get(SyncConfiguration::CFG_DATA_SYNC);
        if (empty($syncCfg))
            return '';

        // Sinon, on va chercher à savoir si le produit est un produit qu'on entretien. 
        // Si nous retrouvons des références fournisseurs pour ce produit, alors c'est le cas
        $id_product = Tools::getValue('id_product');
		if (empty($id_product))
			return '';

        // On va chercher les références fournisseurs
        $refMngr = new SupplierRefManager($id_supplier, [$id_product]);
        if ($refMngr->getNbProducts() == 0)
            return '';    // Non pas géré

        $locked = $this->getCfg()->isProductLocked((int) $id_product);

        $class = $this->name . "-disabled";
        $polarisColor = "#e4a12d";
		$style = "";
		$js = "";

        // Rétribution des informations backoffices depuis la base de données
        $db = \Db::getInstance();
        $sql = "SELECT backoffice_categorization FROM " . $this->TblPrefix . "product WHERE id_product = " . (int) $id_product;
        $value = $db->getValue($sql);
        if ($value)
        {
            $infos = json_decode($value, true);
            if ($infos)
            {                
                // On liste les catégories
                $cats = ['#product_ref' => "Référence"];
                // pour inclure même les méta => foreach (CategoriesManager::getDynCatsTypes($cfg) as $catid => $catname)                    
                foreach ($cfg->get(SyncConfiguration::CFG_DYN_CAT) as $catid => $catname)
                    $cats[$catid] = $catname;

                // On affiche sur 2 colonnes
                $backOfficeInfos = '';
                $backOfficeInfos .= '<div class="ps_bo_infos">';
                $backOfficeInfos .= '<table>';                                        
                foreach ($cats as $cat => $name)
                {                    
                    $backOfficeInfos .= '<tr>';
                    $backOfficeInfos .= '<th>' . htmlspecialchars($name) . '</th>';
                    if ($cat === "#product_ref")
                    {
                        $backOfficeInfos .= '<td><div id="pc_product_id"></div></td>';                            
                    }
                    else
                    {
                        $names = $cfg->get(SyncConfiguration::CFG_MAP_DYN_CAT . $cat . '_name');
                        
                        $backOfficeInfos .= '<td>';

                        if (isset($infos[$cat]))
                        {
                            $n = htmlspecialchars($infos[$cat]);
                            if (isset($names[$infos[$cat]]))
                            {
                                $name = $names[$infos[$cat]];
                                // si le nom commence déjà par la référence, on ne la remet pas
                                if (str_starts_with($name, $n))
                                    $name = trim(substr($name, strlen($n)), ' -');
                                $n .= ' – ' . htmlspecialchars($name);
                            }
                            
                            $backOfficeInfos .= $n;
                        }
                        $backOfficeInfos .= '</td>';
                    }
                    $backOfficeInfos .= '</tr>';
                }

                $backOfficeInfos .= '</table>';
                $backOfficeInfos .= '</div>';
            }
            else
                $backOfficeInfos = 'Informations backoffice non disponibles ?!';            

            // Injection du panneau d'information
            $js .= "$('.product-reference').hide();\n";
            $js .= "$('.product-header-summary').append('" . addslashes($backOfficeInfos) . "');\n";
            $js .= "$('#pc_product_id').html($('.product-reference').find('span').html());\n";            
        }        

        if (!$locked)
        {
            $js .= "$('.product-type-preview').before('<p class=\"".$class."\" >(Les champs de cette couleur sont contrôlés par votre backoffice ".$this->getBridge()->getId()." et <strong>vous ne devez pas les modifier</strong> directement sous Prestashop)</p>');\n";

            $item_to_disable = [
                '.product-type-preview',        // Pas de changement de type de produit
                '#combination-list-actions',    // ni de création de déclinaisons                        
            ];

            $fields_to_disable = [
                '#product_pricing_retail_price_price_tax_included', // Pas d'actions sur les prix
                '#product_pricing_retail_price_price_tax_excluded',            
            ];

            $fields_to_style = [
                '.delete-combination-item',     // pas de suppression de déclinaisons            
                '.edit-combination-item',       // pas de modification de déclinaisons
            ];

            // Cas particulier des caractéristiques
            $features = $this->getCfg()->get(SyncConfiguration::CFG_PR_ATTRIBUTES_MAPPING);
            $caracts = [];
            $id_lang = $this->context->language->id;
            if (!empty($features) && isset($features[SyncConfiguration::PRODUCT_ATTR_FEATURE]))
            {            
                foreach ($features[SyncConfiguration::PRODUCT_ATTR_FEATURE] as $feature => $id_feature)
                {
                    if ($id_feature > 0)
                    {
                        $feature = new Feature($id_feature);
                        $caracts[] = mb_strtolower($feature->name[$id_lang]);
                    }
                }
            }
            
            if (!empty($caracts))
                $js .= "$('#product_details_features_feature_values').before('<p class=\"".$class."\">Attention, certaines caractéristiques (".join(", ", $caracts).") sont maintenues par le module ".$this->name.".<br>Ne modifiez pas ces caractéristiques, le module annulera vos modifications pour ces catégories.</p>');\n";


            // Application des données de synchronisation
            foreach( [
                SyncConfiguration::CFG_PRODUCT_SYNC_REF => '#product_details_references_reference',
                SyncConfiguration::CFG_PRODUCT_SYNC_NAME => '#product_header_name',
                SyncConfiguration::CFG_PRODUCT_SYNC_DESCRIPTION => '#product_description_description',
                SyncConfiguration::CFG_PRODUCT_SYNC_MANUFACTURER => '#product_description_manufacturer',
                SyncConfiguration::CFG_PRODUCT_SYNC_DIMENSIONS => ['#product_shipping_dimensions_width', '#product_shipping_dimensions_height', '#product_shipping_dimensions_depth'],
                SyncConfiguration::CFG_PRODUCT_SYNC_WEIGHT => '#product_shipping_dimensions_weight',
                //SyncConfiguration::CFG_PRODUCT_SYNC_ECOTAX => true,
                //SyncConfiguration::CFG_PRODUCT_SYNC_LOCATION => '#combination_form_stock_options_stock_location',
                SyncConfiguration::CFG_PRODUCT_SYNC_TAXRATE => '#product_pricing_retail_price_tax_rules_group_id',
                SyncConfiguration::CFG_PRODUCT_SYNC_EAN13 => '#product_details_references_ean_13',            
                // SyncConfiguration::CFG_PRODUCT_SYNC_CATEGORIES => '#product_description_categories', => LAISSONS la possibilité au marchand de compléter la fiche comme il le souhaite
                SyncConfiguration::CFG_PRODUCT_SYNC_PHOTOS => '#product_description_images',            
            ] as $k => $v)
            {
                if (isset($syncCfg[$k]) && $syncCfg[$k])
                {
                    if (is_array($v))
                        $fields_to_disable = array_merge($fields_to_disable, $v);
                    else
                        $fields_to_disable[] = $v;
                }
            }

            // Application
            foreach ($item_to_disable as $field)
                $js .= "$('".$field."').attr('tabindex', '-1');\n$('".$field."').addClass('".$class."');\n";

            foreach ($fields_to_disable as $field)
                $js .= "$('".$field."').closest('.form-group').addClass('".$class."');\n"; // Remonter le champs pour mettre la classe sur le premier form-group qui englobe notre classe !            

            // Pour les fields_to_style, on laisse la possibilité tout de même de les modifier, mais on les met en couleur
            foreach ($fields_to_style as $field)
                $style .= $field." { color: $polarisColor !important; border-color: $polarisColor !important;}\n";

            $style .= ".".$class." { pointer-events: none !important; color: $polarisColor !important; border-color: $polarisColor !important;}\n";
            $style .= ".".$class." * { pointer-events: none !important; color: $polarisColor !important; border-color: $polarisColor !important;}\n";
            //$style .= join(", ", $fields_to_hide)." { display: none !important; }\n";
        }

        // Si le produit est verrouillé, on propose la possibilité de le déverrouiller
        if ($locked)
            $js .= '$("#product__toolbar_buttons").prepend(\'<a id="'.$this->name.'_lock" class="toolbar-button btn" style="color: crimson;" title="Ce produit est verrouillé. Il ne sera pas synchronisé depuis '. $this->getBridge()->getId() .'"><i class="material-icons">lock</i><span class="btn-label"></span></a>\');';
        else
        {            
            // Injection d'un bouton de synchronisation sur la fiche produit
            if ($this->getBridge()->canSyncOneProductOnly())
                $js .= '$("#product__toolbar_buttons").prepend(\'<a id="'.$this->name.'_sync" class="toolbar-button btn" style="color: #6c868e;" title="Synchroniser depuis ' . $this->getBridge()->getId() . '"><i class="material-icons">sync</i><span class="btn-label"></span></a>\');';
            $js .= '$("#product__toolbar_buttons").prepend(\'<a id="'.$this->name.'_lock" class="toolbar-button btn" style="color: #6c868e;" title="Empêcher de mettre à jour la fiche depuis '. $this->getBridge()->getId() .'"><i class="material-icons">lock_open</i><span class="btn-label"></span></a>\');';
            $js .= '$("#'.$this->name.'_sync").click((e) => ' . $this->name . '_syncProduct(e, '.$id_product.'));';        
        }

        // Synchroniser les données        
        $js .= '$("#'.$this->name.'_lock").click((e) => ' . $this->name . '_toggleLockProduct(e, '.$id_product.'));';

        $output = "";
        if (!empty($style))
			$output .= "<style>\n".$style."</style>\n";
		if (!empty($js))
			$output .= "<script language='javascript'>\n$(document).ready(function() {".$js."});\n</script>\n";

		return $output;
    }

    /**
	*	On agrémente le panneau d'administration des commandes avec nos fonctions.
    *
    *    @param array $params
	*/
	public function hookActionGetAdminOrderButtons(array $params)
	{
        $id_order = (int) $params['id_order'];
        $bar = $params['actions_bar_buttons_collection'];
        if ($bar && $id_order)
        {
            $psOrder = new \Order($id_order);
            if ($psOrder->id)
            {
                // On instancie un syncer
                $syncer = new SyncerOrders(new Syncer($this));
                $exportable = $syncer->isExportableOrder($psOrder->reference);
                
        	    $bar->add(new \PrestaShop\PrestaShop\Core\Action\ActionsBarButton(
                     'btn-action'.($exportable ? '' : ' disabled'), 
					['onclick' => $this->name.'_exportOrder(this, \'' . $psOrder->reference . '\')'], 
					'Réexporter vers '.$this->getBridge()->getId(),
            	));
            }
		}

		return "";
	}

    /**
     * Montage des hooks maisons !
     * 
     * @param array $params
     * @return string
     */
    public function hookDisplayProductPriceBlock($params) : string
	{
        $build = '';

		if (isset($params["type"]) && $params["type"] == "after_price" && \Configuration::get($this->BridgePrefix."prestaconnector_use_mpm"))
			$build .= $this->hookDisplayMultiProductSelector($params);

        return $build;
	}

    /**
     * Affichage des disponibilités, le cas échéant
     * 
     * @param array $params
     * @return string
     */
    public function hookDisplayProductAdditionalInfo($params) : string
    {
        if (\Configuration::get($this->BridgePrefix."prestaconnector_use_availabilities"))
            return $this->hookDisplayStoreStocks($params);

        return '';
    }

    /**
     * Affichage du produit connexe manuel
     * 
     * @param array $params
     * @return string
     */
	public function hookDisplayMultiProductSelector($params) : string
	{
        if (!isset($params["product"]))
            return '⚠ Renseignez le paramètre `product` pour afficher le connecteur ici ! ⚠';   // Pas de chance

        if (!$params["product"] || !$params["product"]->id_product)
            return '';   // Pas de chance

        // On a besoin d'un gestionnaire de produits connexes
        $mpm = $this->getMpm();
        return $mpm->buildSelector($params["product"]->id_product);
	}
    
    /**
     * Chargement des disponibilités de stock par magasin
     */
    public function hookBuildStoreStocks(& $params)
    {
        if (!$params["product"] || !$params["product"]->id_product)
            return '';   // Pas de chance

        // On a besoin de calculer les stocks, on charge tous les stocks de ce produit
        $stocks = [];

        $db = \Db::getInstance(); 
        $results = $db->executeS('SELECT id_product_attribute, store_code, qty FROM ' . $this->TblPrefix . 'stocks WHERE id_product = '.(int) $params["product"]->id_product.' AND qty > 0');
        if ($results === false)
            return '';   // Pas de chance : BUG

        // Chargement du mappage des magasins
        $stores = $this->getCfg()->get(SyncConfiguration::CFG_MAP_STORES);

        // Chargement des boutiques
        foreach (\Store::getStores(\Context::getContext()->language->id) as $shop)
        {
            $stocks[$shop['id']] = [
                'name' => $shop['name'],
                'qties' => [],
            ];
        }

        foreach ($results as $row)
        {
            // On va chercher le code du magasin dans le mappage
            $store_id = 0;
            if (isset($stores[$row['store_code']]))
                $store_id = $stores[$row['store_code']];
            else if (isset($stores['*']))
                $store_id = $stores['*'];

            if ($store_id > 0)
            {
                $product_attribute_id = (int) $row['id_product_attribute'];
                $qty = (int) $row['qty'];

                if (!isset($stocks[$store_id]['qties'][$product_attribute_id]))
                    $stocks[$store_id]['qties'][$product_attribute_id] = 0;
                $stocks[$store_id]['qties'][$product_attribute_id] += $qty;
            }
        }

        $params['stores_stocks'] = $stocks;
    }

    /**
     * Affichage des disponibilités de stock par magasin
     * 
     * @param array $params    
     * @return string
     */
    public function hookDisplayStoreStocks($params) : string
    {
        // Calcul des stocks
        $this->hookBuildStoreStocks($params);
        $stocks = isset($params['stores_stocks']) ? $params['stores_stocks'] : null;

        $id_product_attribute = 0;
        if (isset($params["product"]))
            $id_product_attribute = (int) $params["product"]->id_product_attribute;            

        $html = '<div class="prestaconnector-stores-stocks">';
        $html .= '<p class="prestaconnector-stores-stocks-title">Disponibilités dans nos magasins&nbsp;:</p>';
        
        $html .= '<div class="details">';

        if ($stocks)
        {
            $trigger = \Configuration::get($this->BridgePrefix . 'prestaconnector_availability_trigger');
            foreach ($stocks as $store_id => $details)
            {
                $html .= '<div class="store">';
                $html .= '<div class="name">' . $details['name'] . '</div>';                

                $qty = isset($details['qties'][$id_product_attribute]) ? $details['qties'][$id_product_attribute] : 0;
                $dispo = 'outofstock';
                $help = "Non disponible";
                if ($qty > 0)
                {
                    $dispo = $qty > $trigger ? 'available' : 'lowstock';
                    $help = $qty > $trigger ? 'Disponible' : 'Attention, dernières pièces disponibles !';
                }

                $html .= '<div class="qty '.$dispo.'"title="'.$help.'">&nbsp;</div>';
                $html .= '</div>';
            }
        }
        
        $html .= '</div>';
        $html .= '</div>';
        
        return $html;
    }

    /**
     * Chargement du fournisseur de références
     * Lève une exception si le fournisseur de référence n'existe pas et ne peut être créé (et associé)
     * 
     * @return int L'identifiant du fournisseur de références
     */
    public function getOrCreateSupplierId() : int
    {
        $cfg = $this->getCfg();

        // Ensuite, on regarde si dans la configuration, nous avons l'identifiant du fournisseur pour ce module
        $supplierId = $cfg->get(SyncConfiguration::CFG_SUPPLIER);
        if ($supplierId)
        {
            // Chargement du fournisseur
            $supplier = new \Supplier($supplierId);
            if ($supplier->id != $supplierId)
            {
                // On log dans prestashop
                \PrestaShopLogger::addLog('Impossible de charger le fournisseur de référence #' . $supplierId . ' => recréation ! ' . $supplier->name, 3, null, "supplier", $supplierId);
                $supplierId = 0;
            }
        }

        if (!$supplierId)
        {
            \PrestaShopLogger::addLog('Fournisseur de références non trouvé ! ', 2);

            $supplier_name = ucfirst($this->getBridge()->getId());

            // On cherche un fournisseur du même nom ?
            $supplierId = \Supplier::getIdByName($supplier_name);
            if (!$supplierId)
            {
                // Toujours pas trouvé : création
                $supplier = new \Supplier();
                // Le nom du fournisseur est le nom qui précède "LCVPrestaConnector" dans le nom du module, avec une majuscule                
                $supplier->name = $supplier_name;
                $supplier->active = 1;
                $supplier->add();

                $supplierId = $supplier->id;
                if (!$supplierId)
                    throw new Exception('Impossible de créer le fournisseur de référence !');                

                \PrestaShopLogger::addLog(sprintf('Création du fournisseur %s pour fournisseur de références', $supplier_name), 1, null, "supplier", $supplierId);

                // Création de l'adresse associée ...
                $address = new \Address();
                $address->id_country = \Configuration::get('PS_COUNTRY_DEFAULT');
                $address->id_state = 0;
                $address->alias = 'Fournisseur de références';
                $address->company = $supplier_name;                
                $address->lastname = '-';
                $address->firstname = '-';
                $address->address1 = '-';
                $address->postcode = '-';
                $address->city = '-';
                $address->id_supplier = $supplierId;
                $address->save();
            }
            else
                \PrestaShopLogger::addLog(sprintf('Utilisation du fournisseur %s pour fournisseur de références', $supplier_name), 1, null, "supplier", $supplierId);

            // Sauvegarde de l'identifiant du fournisseur dans la configuration
            $cfg->set(SyncConfiguration::CFG_SUPPLIER, $supplierId);
        }

        return (int) $supplierId;
    }

    /**
     * Validation d'une commande !
     * 
     */
    public function hookActionValidateOrder($params)
    {
        /** @var Order $order */
        $order = $params['order'];
        if (!Validate::isLoadedObject($order)) return;

        $orderCartRules = $order->getCartRules();
        if (!$orderCartRules) return;

        // Langue ID par défaut
        $id_lang = (int)Configuration::get('PS_LANG_DEFAULT');

        foreach ($orderCartRules as $ocr) {
            $idCartRule = (int)$ocr['id_cart_rule'];
            $usedValue  = (float)$ocr['value']; // montant réellement déduit sur la commande

            $cartRule = new CartRule($idCartRule);
            if (!Validate::isLoadedObject($cartRule)) continue;
            
            $name = is_array($cartRule->name) ? $cartRule->name[$id_lang] : $cartRule->name;
            if (!str_starts_with($name, 'Carte prépayée')) continue;   // Que les cartes réutilisables !

            $original  = (float)$cartRule->reduction_amount;
            $remaining = $original - $usedValue;

            // arrondi PS
            $remaining = (float) Tools::ps_round($remaining, 2);

            if ($remaining <= 0.0) {
                // Plus de solde : tu peux désactiver le bon
                $cartRule->reduction_amount = 0;
                $cartRule->active = 0;
                $cartRule->update();
                continue;
            }

            // 1) Mettre à jour le solde
            $cartRule->reduction_amount = $remaining;

            // 2) Garantir réutilisable (évite blocage "déjà utilisé")
            $cartRule->partial_use = 0;
            $cartRule->quantity = 1;

            $cartRule->update();
        }
    }

    /**
     * Hook de la page de login admin Prestashop.
     *
     * Rend un bouton « Équipe VEGA » ou « Équipe LCV » selon le bridge actif,
     * qui démarre le flow SSO OIDC vers Authentik. Le formulaire de login
     * local reste actif pour les utilisateurs normaux.
     *
     * Ne rend rien si la backdoor est désactivée en configuration.
     */
    public function hookDisplayAdminLogin($params) : string
    {
        try
        {
            $auth = new \PrestaShop\Module\LCVPrestaConnector\OidcAuthenticator($this);
 
            if (!$auth->isEnabled())
                return '';

            $backoffice = $this->getBridge()->getId();
            $label    = htmlspecialchars($auth->getButtonLabel(), ENT_QUOTES, 'UTF-8');
            $loginUrl = htmlspecialchars($auth->getLoginUri(),   ENT_QUOTES, 'UTF-8');
 
            // HTML autonome — pas de dépendance Twig (qui n'est pas garanti
            // d'être initialisé sur la page de login admin).
            return <<<HTML
<div class="pc-sso-login" style="margin: 1.5em; padding-top: 1.5em; border-top: 1px solid #e0e0e0; text-align: center;">
    <p style="font-size: 0.85em; color: #888; margin-bottom: 0.8em;">
        Vous êtes membre de l'équipe support {$backoffice}&nbsp;?
    </p>
    <a href="{$loginUrl}" class="btn btn-primary btn-lg" style="display: inline-block;">
        <i class="material-icons" style="vertical-align: middle; margin-right: 0.3em;">vpn_key</i>
        {$label}
    </a>
</div>
HTML;
        }
        catch (\Throwable $e)
        {
            // Une erreur ici ne doit jamais bloquer la page de login normale
            \PrestaShopLogger::addLog('LCVPrestaConnector SSO : erreur hookDisplayAdminLogin : ' . $e->getMessage(), 2);
            return '';
        }
    }

}