<?php
namespace PrestaShop\Module\LCVPrestaConnector;

/**
 * Gestionnaire de catégories
 * 
 */
class CategoriesManager
{
    /**
     * Synchroniseur
     * 
     * @var Syncer
     */
    private Syncer $syncer;

    /**
     * Catégories dynamiques gérées
     */
    public array $dynCats;

    /**
     * Mapping des catégories dynamiques
     */
    private array $mapping;

    /**
     * Liste exhaustive des catégories Prestashop de destination de mappage
     */
    private array $mappedCats;

    /**
     * Cache des catégories Prestashop
     */
    private array|null $psCatsCache = null;

    /**
     * Retourne les types de catégories dynamiques disponibles pour le mapping
     * 
     */
    public static function & getDynCatsTypes(SyncConfiguration $cfg) : array
    {        
        $cat_types = $cfg->get(SyncConfiguration::CFG_DYN_CAT);

        $meta_categories = $cfg->get(SyncConfiguration::CFG_META_CATEGORIES) ?? null;        
        if ($meta_categories)
        {
            foreach ($meta_categories as $mc_id => $mc_data)
                if ($mc_data)
                    $cat_types[$mc_id] = $mc_data[0];
        }

        return $cat_types;
    }

    /**
     * Reconstruit, pour l'affichage, les libellés des clés composites d'une méta-catégorie.
     *
     * Le libellé n'est plus stocké en configuration (cf. translateCode et
     * BridgeAdapter::pullProducts) : il est recalculé à la volée à partir des libellés des
     * catégories composantes, qui sont eux bornés par la cardinalité de chaque catégorie.
     *
     * Exemple : méta-catégorie « RF » = [Rayon, Famille], clé « 02,05 »
     *           => « <libellé rayon 02>, <libellé famille 05> ».
     *
     * @param SyncConfiguration $cfg
     * @param string $metaCatId Identifiant de la méta-catégorie (ex. "metacat_1")
     * @param array<string> $compositeKeys Clés composites à libeller (ex. ["02,05", "*", ...])
     * @return array<string,string> [ clé composite => libellé reconstruit ]
     */
    public static function & buildMetaCategoryLabels(SyncConfiguration $cfg, string $metaCatId, array $compositeKeys) : array
    {
        $labels = [];

        // Définition de la méta-catégorie : [nom, code_cat_1, code_cat_2, ...]
        $metaCats = $cfg->get(SyncConfiguration::CFG_META_CATEGORIES) ?? [];
        $definition = $metaCats[$metaCatId] ?? null;
        if (!$definition)
            return $labels;

        // Codes des catégories composantes (on saute le premier élément = nom de la méta-catégorie)
        $components = array_slice($definition, 1);

        // Préchargement (une seule fois) des maps de libellés de chaque composante
        $nameMaps = [];
        foreach ($components as $code)
        {
            if ($code === '__manufacturer')
                $nameMaps[$code] = $cfg->get(SyncConfiguration::CFG_MAP_MANUFACTURER_NAME) ?? [];
            else
                $nameMaps[$code] = $cfg->get(SyncConfiguration::CFG_MAP_DYN_CAT . $code . '_name') ?? [];
        }

        foreach ($compositeKeys as $compositeKey)
        {
            if ($compositeKey === '*')
            {
                $labels[$compositeKey] = 'Toutes les autres';
                continue;
            }

            // La clé composite est alignée positionnellement avec les composantes
            $parts = explode(',', $compositeKey);

            $bits = [];
            foreach ($components as $idx => $code)
            {
                $part = $parts[$idx] ?? '';
                if ($part === '')
                    continue;

                $label = $nameMaps[$code][$part] ?? null;

                if ($code !== '__manufacturer' && $label)
                {
                    // Les maps de catégories dynamiques stockent "code - libellé" : on ne garde que le libellé
                    $split = explode(' - ', $label, 2);
                    if (count($split) > 1)
                        $label = $split[1];
                }

                $bits[] = ($label !== null && $label !== '') ? $label : $part;
            }

            // Format historique : "code composite - libellé composite" (ex. "02,05 - Chemises, Manches longues")
            $labels[$compositeKey] = implode(', ', $parts) . ' - ' . implode(', ', $bits);
        }

        return $labels;
    }

    /**
     * Nouvelle instance
     *
     * @param Syncer $syncer Synchroniseur
     */
    public function __construct(Syncer $syncer, ?array $productsId = null)
    {
        $this->syncer = $syncer;
        // Chargement des mappings, pour chaque catégorie dynamique
        $mapping = [];
        $cfg = $this->syncer->module->getCfg();

        $this->dynCats = self::getDynCatsTypes($cfg);

        foreach ($this->dynCats as $cat => $name)
        {
            $mapping[$cat] = $cfg->get(SyncConfiguration::CFG_MAP_DYN_CAT . $cat);
            if (!isset($mapping[$cat]))
                $mapping[$cat] = [];

            // On convertit les ID en int
            foreach ($mapping[$cat] as $code => $id_category)
            {
                $intmap = [];
                if (is_array($id_category))
                {
                    foreach ($id_category as $id)
                        $intmap[] = (int) $id;
                }
                else
                    $intmap[] = (int) $id_category;

                $mapping[$cat][$code] = $intmap;
            }
        }
        
        $this->mapping = $mapping;
        $this->mappedCats = [];
        foreach ($mapping as $cat => $map)
        {
            foreach ($map as $code => $id_category)
            {
                if (is_array($id_category))
                {
                    foreach ($id_category as $id)
                        $this->mappedCats[$id] = true;
                }
                else
                    $this->mappedCats[$id_category] = true;
            }
        }
    }

    /*
     * Détermine si l'id de la catégorie renseignée est le résultat
     * probable d'un mappage
     */
    public function isMappedCategory(int $id_category) : bool
    {
        return isset($this->mappedCats[$id_category]) && $this->mappedCats[$id_category];
    }

    /**
     * Traduit un code de catégorie en ID de catégories Prestashop selon le mapping prévu
     * 
     * @param string $category Catégorie dynamique
     * @param string $code Code
     * @param string|null $libelle Libellé de la catégorie (optionnel, pour aider à l'auto-création)
     * @return array<int>ID de catégories
     */
    public function translateCode($category, $code, $libelle = null) : array
    {
        if (isset($this->mapping[$category]))
        {
            $id_category = 0;
        
            if (isset($this->mapping[$category][$code]))
            {
                $id_category = $this->mapping[$category][$code];
            }
            else if ($libelle) {
                // Code absent de la liste, auto-création ?
                $cfg = $this->syncer->module->getCfg();

                // Les libellés des méta-catégories ne sont pas stockés : ils sont reconstruits
                // à l'affichage à partir des catégories composantes (cf. buildMetaCategoryLabels).
                $isMetaCat = str_starts_with($category, 'metacat_');

                // Cas spécial => entièrement vide, on crée au moins le catch-all
                if (count($this->mapping[$category]) == 0)
                {
                    $this->mapping[$category]['*'] = 0;
                    $cfg->set(SyncConfiguration::CFG_MAP_DYN_CAT . $category, ['*' => 0]);   // par défaut, on mappe sur rien
                    if (!$isMetaCat)
                        $cfg->set(SyncConfiguration::CFG_MAP_DYN_CAT . $category . '_name', ['*' => 'Toutes les entrées']);   // par défaut, on mappe sur rien
                }

                $this->mapping[$category][$code] = 0;   // par défaut, on mappe sur rien

                $cfg->setMap(SyncConfiguration::CFG_MAP_DYN_CAT . $category, $code, 0);
                if (!$isMetaCat)
                    $cfg->setMap(SyncConfiguration::CFG_MAP_DYN_CAT . $category . '_name', $code, $code . ' - ' . $libelle);
            }

            if ($id_category)
            {
                if (is_array($id_category))
                {
                    if (count($id_category) > 0 && (count($id_category) > 1 || $id_category[0] != 0))
                        return $id_category;
                    if ($code != '*')
                        return $this->translateCode($category, '*');  // catch-all !
                }
                else
                    return [$id_category];
            }
            else if ($code != '*')
                return $this->translateCode($category, '*');  // catch-all !            
        }

        return [];
    }    

    /**
     * Contrôle que les catégories passées en paramètre sont les mêmes que celles du produit
     * 
     * @param array<int> $cats Catégories à vérifier
     * @param array<int> $productCats Catégories du produit
     * @return bool Vrai si les catégories sont les mêmes     
     */
    public function isCatsTheSame(array $cats, array $productCats) : bool
    {
        // On compare que les tableaux sont équivalents (quelque soit l'ordre)        
        if (count($productCats) != count($cats))
            return false;

        foreach ($cats as $cat)
        {
            if (!in_array($cat, $productCats))
                return false;
        }

        foreach ($productCats as $cat)
        {
            if (!in_array($cat, $cats))
                return false;
        }

        return true;    // Ca a l'air ok !
    }

    /**
     * Obtient un tableau de parents pour une catégorie prestashop, dans l'ordre de la hiérarchie (mère, grand-mère, etc...)
     * 
     * @param int $id_category ID de la catégorie Prestashop
     * @return array parents
     */
    public function getPsCategoryTree(int $id_category) : array
    {
        // Cache non chargé, on le charge
        if ($this->psCatsCache === null)
        {
            $this->psCatsCache = [];

            $result = \Db::getInstance()->executeS('SELECT c.`id_category`, c.`id_parent` FROM `' . _DB_PREFIX_ . 'category` c');

            foreach ($result as $row)
                $this->psCatsCache[$row['id_category']] = $row['id_parent'];
        }

        // Construction de l'arbre de catégories
        $tree = [];
        while ($id_category > 0)
        {
            $tree[] = $id_category;
            $id_category = isset($this->psCatsCache[$id_category]) ? $this->psCatsCache[$id_category] : 0;
        }

        return $tree;
    }
}