<?php
// +-------------------------------------------------+
// © 2002-2004 PMB Services / www.sigb.net pmb@sigb.net et contributeurs (voir www.sigb.net)
// +-------------------------------------------------+
// $Id: TextHelper.php,v 1.1.2.2 2026/04/30 08:05:03 qvarin Exp $

namespace Pmb\Common\Helper;

if (stristr($_SERVER['REQUEST_URI'], basename(__FILE__))) {
    die("no access");
}

use encoding_normalize;
use InvalidArgumentException;
use RuntimeException;

class TextHelper
{
    /**
     * Liste des mots vides (stop words)
     *
     * @var null|string[]
     */
    private static $stopWords = null;

    /**
     * C_TRIM: Supprime les espaces en debut et fin de chaine
     *
     * @var int
     */
    public const C_TRIM = 1 << 0;

    /**
     * C_PONCT: Remplace les ponctuations par des espaces
     *
     * @var int
     */
    public const C_PONCT = 1 << 1;

    /**
     * C_HTML: Supprime les balises HTML
     *
     * @var int
     */
    public const C_HTML = 1 << 2;

    /**
     * C_NEW_LINE: Remplace les sauts de ligne par des espaces
     *
     * @var int
     */
    public const C_NEW_LINE = 1 << 3;

    /**
     * C_ACCENTED_CHARACTERS: Supprime les caractères accentués
     *
     * @var int
     */
    public const C_ACCENTED_CHARACTERS = 1 << 4;

    /**
     * C_ALL: Tous les nettoyages
     *
     * @var int
     */
    public const C_ALL = self::C_TRIM | self::C_PONCT | self::C_HTML | self::C_NEW_LINE | self::C_ACCENTED_CHARACTERS;

    /**
     * Nettoie la chaine donnee
     *
     * @param string $string La chaine a nettoyer
     * @param int $clean (Combinaison des constantes de nettoyage TextHelper::C_*)
     * @return string La chaine nettoyee
     *
     * @throws InvalidArgumentException Si la chaine est vide
     */
    public static function clean(string $string, int $clean): string
    {
        if (empty(trim($string))) {
            throw new InvalidArgumentException('String cannot be empty');
        }

        if ($clean <= 0) {
            trigger_error('No cleaning mode selected.', E_USER_WARNING);
            return $string;
        }

        if ($clean & self::C_HTML) {
            $string = strip_tags($string);
        }
        if ($clean & self::C_NEW_LINE) {
            $string = str_replace(["\r\n", "\n"], ' ', $string);
        }
        if ($clean & self::C_ACCENTED_CHARACTERS) {
            $string = htmlentities($string, ENT_NOQUOTES, 'utf-8');
            $string = preg_replace('#&([a-z])(?:acute|cedil|caron|circ|grave|orn|ring|slash|th|tilde|uml);#i', '\1', $string);
            $string = preg_replace('#&([a-z]{2})(?:lig);#i', '\1', $string);
            $string = html_entity_decode($string);
        }
        if ($clean & self::C_PONCT) {
            $patterns = [
                mb_chr(0x2019, 'UTF-8') => "'",
                mb_chr(0x00AB, 'UTF-8') => '"',
                mb_chr(0x00BB, 'UTF-8') => '"',
                mb_chr(0x201E, 'UTF-8') => '"',
                mb_chr(0x2013, 'UTF-8') => '-',
                mb_chr(0x2014, 'UTF-8') => '-',
                mb_chr(0x2026, 'UTF-8') => '...',
            ];
            $string = str_replace(array_keys($patterns), array_values($patterns), $string);
            $string = preg_replace('#([[:punct:]])#', ' ', $string);
        }
        if ($clean & self::C_TRIM) {
            $string = preg_replace('#[ ]{2,}#', ' ', $string);
            $string = trim($string);
        }
        return $string;
    }

    /**
     * Convertit la chaine donnee en UTF-8
     *
     * @param string $string La chaine a convertir
     * @return string La chaine convertie
     *
     * @throws InvalidArgumentException Si la chaine est vide
     * @throws RuntimeException Si l'encodage ne peut pas etre detecte
     */
    public static function toUTF8(string $string): string
    {
        if (empty(trim($string))) {
            throw new InvalidArgumentException('String cannot be empty');
        }

        $encoding = encoding_normalize::detect_encoding($string, mb_detect_order());
        if ($encoding === false) {
            throw new RuntimeException("Could not detect encoding for string: " . $string);
        }

        if (strtolower($encoding) === 'utf-8') {
            return $string;
        }
        return encoding_normalize::convert($string, 'UTF-8', $encoding);
    }

    /**
     * Normalise la chaine donnee
     *
     * @param string $string La chaine a normaliser
     * @param int $clean Le type de nettoyage a appliquer (par defaut C_TRIM)
     * @return string La chaine normalisee
     *
     * @throws InvalidArgumentException Si la chaine est vide
     */
    public static function normalize(string $string, int $clean = self::C_TRIM): string
    {
        if (empty(trim($string))) {
            throw new InvalidArgumentException('String cannot be empty');
        }

        $string = strtolower($string);
        $string = self::toUTF8($string);
        return self::clean($string, $clean);
    }

    /**
     * Retourne la liste des mots vides (stop words)
     *
     * @return array
     */
    public static function stopWords(): array
    {
        if (self::$stopWords === null) {
            global $empty_word;
            if (!isset($empty_word) || !is_array($empty_word)) {
                global $include_path, $pmb_indexation_lang, $lang;

                $lang ??= 'fr_FR';
                $pmb_indexation_lang ??= $lang;

                $empty_word = [];
                $pmb_indexation_lang = basename($pmb_indexation_lang);
                if (is_dir("$include_path/marc_tables/$pmb_indexation_lang")) {
                    require_once "$include_path/marc_tables/$pmb_indexation_lang/empty_words";
                }
            }
            self::$stopWords = array_filter($empty_word, fn ($word) => is_string($word) && trim($word) !== '');
        }
        return self::$stopWords;
    }
}