<?php

/**
 * Utilidades para clasificar productos en categorías basadas en el nombre.
 *
 * - Crea categorías nivel 1 y nivel 2 si no existen.
 * - Clasifica el nombre del producto usando reglas de palabras clave configurables.
 */

declare(strict_types=1);

/**
 * Lista de relaciones categoría nivel 2 => categoría nivel 1.
 *
 * Importante: mantener las llaves exactamente como figuran en la base de datos/UI.
 *
 * @return array<string,string>
 */
function categoria_nivel2_a_nivel1(): array
{
    return [
        'Camas' => 'Muebles',
        'Butacas' => 'Muebles',
        'Mesas de centro' => 'Muebles',
        'Zapatero' => 'Muebles',
        'Puf' => 'Muebles',
        'Mesas de noche' => 'Muebles',
        'Productos cuidado m' => 'Muebles',
        'Escritorio' => 'Muebles',
        'Sistemas de pared' => 'Muebles',
        'Bancos' => 'Muebles',
        'Sofácamas' => 'Muebles',
        'Sofás' => 'Muebles',
        'Mesas' => 'Muebles',
        'Mesas Auxiliares' => 'Muebles',
        'Sillas' => 'Muebles',
        'Almacenamiento' => 'Muebles',
        'Cuadros' => 'Accesorios',
        'Lamparas' => 'Accesorios',
        'Alfombras' => 'Accesorios',
        'Cojines' => 'Accesorios',
        'Objetos Decorativos' => 'Accesorios',
        'No venta' => 'Interno',
    ];
}

/**
 * Normaliza cadenas para facilitar la comparación:
 * - Minúsculas
 * - Elimina/convierte tildes y caracteres especiales comunes
 * - Reduce espacios y símbolos redundantes
 */
function normalize_text(string $text): string
{
    $text = trim($text);
    if ($text === '') {
        return '';
    }

    if (function_exists('mb_strtolower')) {
        $text = mb_strtolower($text, 'UTF-8');
    } else {
        $text = strtolower($text);
    }

    if (class_exists('\Normalizer')) {
        $normalized = \Normalizer::normalize($text, \Normalizer::FORM_D);
        if ($normalized !== false) {
            $stripped = preg_replace('/\p{Mn}+/u', '', $normalized);
            if ($stripped !== null) {
                $text = $stripped;
            } else {
                $text = $normalized;
            }
        }
    }

    if (function_exists('iconv')) {
        $transliterated = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $text);
        if ($transliterated !== false && $transliterated !== '') {
            $text = $transliterated;
        }
    }

    $text = strtr($text, [
        'á' => 'a',
        'é' => 'e',
        'í' => 'i',
        'ó' => 'o',
        'ú' => 'u',
        'ñ' => 'n',
        'ü' => 'u',
        'ä' => 'a',
        'ë' => 'e',
        'ï' => 'i',
        'ö' => 'o',
        'ÿ' => 'y',
        'â' => 'a',
        'ê' => 'e',
        'î' => 'i',
        'ô' => 'o',
        'û' => 'u',
        'å' => 'a',
        'ø' => 'o',
        'œ' => 'oe',
        'æ' => 'ae',
        'ç' => 'c',
        'ß' => 'ss',
        'ð' => 'd',
    ]);

    $text = preg_replace('/[^a-z0-9\s]/', ' ', $text) ?? $text;
    $text = preg_replace('/\s+/', ' ', $text) ?? $text;

    return trim($text);
}

/**
 * Devuelve las reglas de clasificación para categorías nivel 2 basadas en palabras clave.
 *
 * Cada regla es un arreglo con:
 * - category2: nombre de la categoría nivel 2.
 * - any: lista de frases/palabras que, si se encuentran, disparan la regla.
 * - all: lista de frases que deben aparecer (opcional).
 * - not: lista de frases que anulan la coincidencia (opcional).
 * - priority: número entero (menor = se evalúa antes).
 *
 * @return array<int,array<string,mixed>>
 */
function build_category_rules(): array
{
    $rules = [
        [
            'category2' => 'No venta',
            'any' => [
                'catalogo', 'catalogue', 'manual', 'muestra', 'swatch', 'sample',
                'setup', 'display', 'no venta', 'visual', 'marketing', 'give-away',
                'poster', 'flyer', 'actividad', 'activity', 'booklet', 'price book',
            ],
            'priority' => 5,
        ],
        [
            'category2' => 'Productos cuidado m',
            'any' => ['care', 'limpieza', 'cleaner', 'maintenance', 'cuidado', 'kit de cuidado'],
            'priority' => 10,
        ],
        [
            'category2' => 'Sofácamas',
            'any' => ['sofa cama', 'sofá cama', 'sofácama', 'sofacama', 'sofabed', 'sofa bed', 'sleep sofa', 'convertible'],
            'priority' => 10,
        ],
        [
            'category2' => 'Sofás',
            'any' => ['sofa', 'sofá', 'sectional', 'chaise', 'loveseat', 'corner'],
            'not' => ['cama', 'bed', 'cube', 'pouf', 'puf'],
            'priority' => 20,
        ],
        [
            'category2' => 'Camas',
            'any' => ['cama', 'bed', 'cabecero', 'headboard', 'somier', 'mattress', 'bedframe'],
            'priority' => 20,
        ],
        [
            'category2' => 'Mesas de centro',
            'any' => ['mesa de centro', 'mesa centro', 'coffee table', 'centro'],
            'priority' => 25,
        ],
        [
            'category2' => 'Mesas Auxiliares',
            'any' => ['auxiliar', 'side table', 'end table', 'mesita auxiliar'],
            'not' => ['noche'],
            'priority' => 30,
        ],
        [
            'category2' => 'Mesas de noche',
            'any' => ['mesita de noche', 'mesa de noche', 'mesilla', 'nightstand', 'bedside'],
            'priority' => 30,
        ],
        [
            'category2' => 'Mesas',
            'any' => ['mesa', 'table', 'dining', 'comedor', 'office table'],
            'not' => ['centro', 'noche', 'auxiliar', 'side', 'coffee', 'desk'],
            'priority' => 35,
        ],
        [
            'category2' => 'Sillas',
            'any' => ['silla', 'chair', 'stool', 'taburete', 'butaca comedor'],
            'not' => ['bench', 'bank', 'sofa'],
            'priority' => 40,
        ],
        [
            'category2' => 'Butacas',
            'any' => ['butaca', 'reclin', 'armchair', 'lounger', 'wing chair'],
            'priority' => 40,
        ],
        [
            'category2' => 'Bancos',
            'any' => ['banco', 'bench', 'banqueta'],
            'priority' => 45,
        ],
        [
            'category2' => 'Puf',
            'any' => ['puf', 'pouf', 'puff', 'ottoman', 'footstool'],
            'priority' => 45,
        ],
        [
            'category2' => 'Lamparas',
            'any' => ['lampara', 'lámpara', 'lamp', 'pendant', 'aplique', 'floorlamp', 'tablelamp'],
            'priority' => 50,
        ],
        [
            'category2' => 'Cuadros',
            'any' => ['cuadro', 'art', 'arte', 'gallery', 'poster', 'print', 'painting'],
            'not' => ['catalogo', 'manual'],
            'priority' => 55,
        ],
        [
            'category2' => 'Alfombras',
            'any' => ['alfombra', 'rug', 'carpet'],
            'priority' => 60,
        ],
        [
            'category2' => 'Cojines',
            'any' => ['cojin', 'cojín', 'pillow', 'cushion'],
            'priority' => 60,
        ],
        [
            'category2' => 'Objetos Decorativos',
            'any' => [
                'decor', 'decorativo', 'vase', 'jarron', 'jarrón', 'figura', 'escultura',
                'bowl', 'bandeja', 'reloj', 'candelabro', 'vela', 'centrepiece', 'centerpiece',
            ],
            'priority' => 65,
        ],
        [
            'category2' => 'Escritorio',
            'any' => ['escritorio', 'desk', 'office desk', 'writing desk'],
            'priority' => 70,
        ],
        [
            'category2' => 'Sistemas de pared',
            'any' => ['sistema de pared', 'wall system', 'wall-unit', 'wall unit', 'estanteria modular', 'shelf system'],
            'priority' => 75,
        ],
        [
            'category2' => 'Almacenamiento',
            'any' => ['almacenamiento', 'storage', 'armario', 'wardrobe', 'gabinete', 'cabinet', 'aparador', 'buffet', 'comoda', 'cómoda', 'vitrina', 'bookcase'],
            'priority' => 80,
        ],
        [
            'category2' => 'Zapatero',
            'any' => ['zapatero', 'shoe cabinet', 'shoe storage'],
            'priority' => 85,
        ],
    ];

    usort($rules, static function ($a, $b) {
        return ($a['priority'] ?? 100) <=> ($b['priority'] ?? 100);
    });

    foreach ($rules as &$rule) {
        if (isset($rule['any'])) {
            $normalizedAny = array_map('normalize_text', (array) $rule['any']);
            $rule['normalized_any'] = array_values(array_filter($normalizedAny, static function ($value) {
                return $value !== '';
            }));
        }
        if (isset($rule['all'])) {
            $normalizedAll = array_map('normalize_text', (array) $rule['all']);
            $rule['normalized_all'] = array_values(array_filter($normalizedAll, static function ($value) {
                return $value !== '';
            }));
        }
        if (isset($rule['not'])) {
            $normalizedNot = array_map('normalize_text', (array) $rule['not']);
            $rule['normalized_not'] = array_values(array_filter($normalizedNot, static function ($value) {
                return $value !== '';
            }));
        }
    }
    unset($rule);

    return $rules;
}

/**
 * Busca (y crea en caso necesario) las categorías configuradas.
 *
 * @return array{cat1: array<string,array{id:int}>, cat2: array<string,array{id:int,categoria1_id:int}>}
 */
function ensure_categories(PDO $pdo, array &$logs): array
{
    $cat1_map = [];
    $cat2_map = [];

    foreach (array_unique(array_values(categoria_nivel2_a_nivel1())) as $cat1_name) {
        $stmt = $pdo->prepare('SELECT id FROM categorias_nivel1 WHERE LOWER(nombre) = LOWER(?) LIMIT 1');
        $stmt->execute([$cat1_name]);
        $id = $stmt->fetchColumn();
        if (!$id) {
            $insert = $pdo->prepare('INSERT INTO categorias_nivel1 (nombre, orden, activo) VALUES (?, ?, 1)');
            $insert->execute([$cat1_name, 99]);
            $id = (int) $pdo->lastInsertId();
            $logs[] = "[INFO] Creada categoría nivel 1 '{$cat1_name}' (id {$id})";
        }
        $cat1_map[$cat1_name] = ['id' => (int) $id];
    }

    foreach (categoria_nivel2_a_nivel1() as $cat2_name => $cat1_name) {
        $cat1_id = $cat1_map[$cat1_name]['id'];

        // Buscar coincidencia exacta por nombre y categoría 1
        $stmt = $pdo->prepare('
            SELECT id
            FROM categorias_nivel2
            WHERE LOWER(TRIM(nombre)) = LOWER(TRIM(?)) AND categoria1_id = ?
            LIMIT 1
        ');
        $stmt->execute([$cat2_name, $cat1_id]);
        $id = $stmt->fetchColumn();

        if (!$id) {
            // Buscar coincidencia por nombre sin importar la categoría para reutilizarla
            $stmt = $pdo->prepare('
                SELECT id, categoria1_id
                FROM categorias_nivel2
                WHERE LOWER(TRIM(nombre)) = LOWER(TRIM(?))
                LIMIT 1
            ');
            $stmt->execute([$cat2_name]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);

            if ($row) {
                $id = (int) $row['id'];
                if ((int) $row['categoria1_id'] !== $cat1_id) {
                    $update = $pdo->prepare('UPDATE categorias_nivel2 SET categoria1_id = ? WHERE id = ?');
                    $update->execute([$cat1_id, $id]);
                    $logs[] = "[INFO] Actualizada subcategoría '{$cat2_name}' para apuntar a '{$cat1_name}' (id {$id})";
                }
            } else {
                $insert = $pdo->prepare('INSERT INTO categorias_nivel2 (categoria1_id, nombre, orden, activo) VALUES (?, ?, ?, 1)');
                $insert->execute([$cat1_id, trim($cat2_name), 99]);
                $id = (int) $pdo->lastInsertId();
                $logs[] = "[INFO] Creada subcategoría '{$cat2_name}' bajo '{$cat1_name}' (id {$id})";
            }
        }

        $cat2_map[$cat2_name] = ['id' => (int) $id, 'categoria1_id' => $cat1_id];
    }

    return ['cat1' => $cat1_map, 'cat2' => $cat2_map];
}

/**
 * Clasifica un nombre y devuelve la categoría nivel 2 si hay coincidencia.
 */
function classify_category2(string $nombre, array $rules): ?string
{
    $normalized = normalize_text($nombre);
    if ($normalized === '') {
        return null;
    }

    foreach ($rules as $rule) {
        $anyFragments = $rule['normalized_any'] ?? [];
        if (!empty($rule['any']) && empty($anyFragments)) {
            $anyFragments = array_map('normalize_text', (array) $rule['any']);
            $anyFragments = array_values(array_filter($anyFragments, static function ($value) {
                return $value !== '';
            }));
        }

        $matchedAny = empty($anyFragments);
        foreach ($anyFragments as $fragment) {
            if ($fragment !== '' && strpos($normalized, $fragment) !== false) {
                $matchedAny = true;
                break;
            }
        }

        if (!empty($anyFragments) && !$matchedAny) {
            continue;
        }

        $allFragments = $rule['normalized_all'] ?? [];
        if (!empty($rule['all']) && empty($allFragments)) {
            $allFragments = array_map('normalize_text', (array) $rule['all']);
            $allFragments = array_values(array_filter($allFragments, static function ($value) {
                return $value !== '';
            }));
        }

        foreach ($allFragments as $fragment) {
            if ($fragment === '' || strpos($normalized, $fragment) === false) {
                continue 2;
            }
        }

        $notFragments = $rule['normalized_not'] ?? [];
        if (!empty($rule['not']) && empty($notFragments)) {
            $notFragments = array_map('normalize_text', (array) $rule['not']);
            $notFragments = array_values(array_filter($notFragments, static function ($value) {
                return $value !== '';
            }));
        }

        $blocked = false;
        foreach ($notFragments as $fragment) {
            if ($fragment !== '' && strpos($normalized, $fragment) !== false) {
                $blocked = true;
                break;
            }
        }
        if ($blocked) {
            continue;
        }

        return $rule['category2'];
    }

    return null;
}

/**
 * Obtiene los mapeos manuales activos por grupo de artículo.
 *
 * @return array<string,array{categoria1_id:int,categoria2_id:?int}>
 */
function obtener_mapeos_activos(PDO $pdo): array
{
    $stmt = $pdo->query('SELECT grupo_articulo, categoria1_id, categoria2_id, activo FROM categoria_mapping');
    $result = [];
    foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
        $grupo = isset($row['grupo_articulo']) ? trim((string) $row['grupo_articulo']) : '';
        if ($grupo !== '' && (!isset($row['activo']) || (int) $row['activo'] === 1)) {
            $result[$grupo] = [
                'categoria1_id' => (int) $row['categoria1_id'],
                'categoria2_id' => isset($row['categoria2_id']) ? (int) $row['categoria2_id'] : null,
            ];
        }
    }
    return $result;
}

/**
 * Construye el texto a utilizar para la clasificación a partir de los campos disponibles.
 */
function obtener_texto_para_clasificar(array $producto): string
{
    $campos = [
        'nombre',
        'descripcion',
        'descripcion_corta',
        'descripcion_larga',
        'description',
        'description_es',
        'titulo',
    ];

    $fragmentos = [];
    foreach ($campos as $campo) {
        if (isset($producto[$campo]) && is_string($producto[$campo])) {
            $valor = trim($producto[$campo]);
            if ($valor !== '') {
                $fragmentos[] = $valor;
            }
        }
    }

    if (empty($fragmentos)) {
        return '';
    }

    $fragmentos = array_unique($fragmentos);
    return trim(implode(' ', $fragmentos));
}

/**
 * Clasifica una colección de productos y actualiza sus categorías.
 *
 * @param array<int,array<string,mixed>> $productos lista con keys referencia, nombre, grupo_articulo, categoria1_id, categoria2_id
 *
 * @return array{actualizados:int, por_mapping:int, por_reglas:int, por_defecto:int, sin_nombre:int, sin_clasificar:array<int,array<string,string>>}
 */
function aplicar_clasificacion_a_productos(
    PDO $pdo,
    array $productos,
    array $catMaps,
    array $rules,
    array $mapeos,
    ?int $defaultCat1,
    ?int $defaultCat2
): array {
    $updateStmt = $pdo->prepare('UPDATE references_data SET categoria1_id = ?, categoria2_id = ? WHERE referencia = ?');

    $stats = [
        'actualizados' => 0,
        'por_mapping' => 0,
        'por_reglas' => 0,
        'por_defecto' => 0,
        'sin_nombre' => 0,
        'sin_clasificar' => [],
    ];

    foreach ($productos as $producto) {
        $referencia = $producto['referencia'];
        $nombre = $producto['nombre'] ?? '';
        $grupo = isset($producto['grupo_articulo']) ? trim((string) $producto['grupo_articulo']) : '';
        $cat1Actual = $producto['categoria1_id'] ?? null;
        $cat2Actual = $producto['categoria2_id'] ?? null;

        $nuevoCat1 = $cat1Actual;
        $nuevoCat2 = $cat2Actual;
        $origen = null;

        if ($grupo && isset($mapeos[$grupo])) {
            $nuevoCat1 = $mapeos[$grupo]['categoria1_id'];
            $nuevoCat2 = $mapeos[$grupo]['categoria2_id'];
            $origen = 'mapping';
        } else {
            if (trim((string) $nombre) === '') {
                $stats['sin_nombre']++;
            }

            $textoClasificacion = obtener_texto_para_clasificar($producto);
            if ($textoClasificacion !== '') {
                $cat2 = classify_category2($textoClasificacion, $rules);
                if ($cat2 && isset($catMaps['cat2'][$cat2])) {
                    $nuevoCat2 = $catMaps['cat2'][$cat2]['id'];
                    $nuevoCat1 = $catMaps['cat2'][$cat2]['categoria1_id'];
                    $origen = 'reglas';
                }
            }
        }

        if ($origen === null) {
            if ($defaultCat1) {
                $nuevoCat1 = $defaultCat1;
                $nuevoCat2 = $defaultCat2;
                $origen = 'defecto';
            } else {
                $stats['sin_clasificar'][] = [
                    'referencia' => (string) $referencia,
                    'nombre' => (string) $nombre,
                ];
            }
        }

        $nuevoCat1 = $nuevoCat1 !== null ? (int) $nuevoCat1 : null;
        $nuevoCat2 = $nuevoCat2 !== null ? (int) $nuevoCat2 : null;

        if ($nuevoCat1 !== $cat1Actual || $nuevoCat2 !== $cat2Actual) {
            $updateStmt->execute([$nuevoCat1, $nuevoCat2, $referencia]);
            $stats['actualizados']++;
        }

        if ($origen === 'mapping') {
            $stats['por_mapping']++;
        } elseif ($origen === 'reglas') {
            $stats['por_reglas']++;
        } elseif ($origen === 'defecto') {
            $stats['por_defecto']++;
        }
    }

    return $stats;
}


