<?php
/**
 * String Helper Class
 *
 * @version     1.0.$Revision:$
 * @version     SVN: $Id:$
 * @package     bplan-base/globals
 * @subpackage  Support
 * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
 * @copyright   Copyright (C) 2025, 2026 bplan-solutions GmbH & Co. KG <https://www.bplan-solutions.de/>
 * /Δ\
 */

namespace BplanBase\Globals\Support;


use BplanBase\Globals\Enums\CaseStyle;
use BplanBase\Globals\Enums\Number;
use BplanBase\Globals\Repositories\CountryRepository;
use Illuminate\Support\Str as IlluminateStr;
use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumberFormat;
use libphonenumber\PhoneNumberUtil;


/**
 * String Helper Class
 *
 * Ergänzende String-Hilfsmethoden, die in Laravel's Illuminate\Support\Str
 * nicht vorhanden sind.
 *
 * @version     1.0.0 / 2026-05-10
 * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
 */
class Str
{


/* +++ TRAITS +++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */


/* +++ CLASS CONSTANTS +++ ++++++++++++++++++++++++++++++++++++++++++++++++++ */


    /**
     * @var     string
     */
    public const DISTINCTIVE_LETTERS = 'ACDEFGHJKMNPQRTUVWXYZ';


/* +++ OBJECT MEMBERS +++ +++++++++++++++++++++++++++++++++++++++++++++++++++ */


/* +++ CLASS MEMBERS +++ ++++++++++++++++++++++++++++++++++++++++++++++++++++ */


/* +++ OBJECT METHODS +++ +++++++++++++++++++++++++++++++++++++++++++++++++++ */


/* +++ CLASS METHODS +++ ++++++++++++++++++++++++++++++++++++++++++++++++++++ */


    /**
     *
     * @param       integer $bytes
     *
     * @param       integer $precision
     *
     * @return      string
     *
     * @version     1.0.0 / 2026-05-10
     * @author      Emilio Cannarozzo <emilio.cannarozzo@bplan-solutions.de>
     */
    public static function bytesToHuman(int $bytes, int $precision = 2): string
    {
        $units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];

        for ($i = 0; $bytes > 1024; $i++) {
            $bytes /= 1024;
        }
        return round($bytes, $precision).' '.$units[$i];

    } // bytesToHuman()


    /**
     * Extrahiert die Ländervorwahl aus einer Telefonnummer
     *
     * Als Eingabe wird eine normalisierte Telefonnummer mit führendem Pluszeichen (z.B. +491638441315) erwartet.
     *
     * @param       null|string $phoneNumber
     *
     * @param       mixed $returnOnEmpty
     *
     * @return      mixed Liefert im Normalfall einen Array mit den Schlüsseln "country-code" und "phone-number".
     *              Die Werte entsprechen den Einzelteilen der übergebenen Telefonnummer oder NULL.
     *
     * @version     1.0.0 / 2026-05-10
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public static function explodePhoneNumber(null|string $phoneNumber, mixed $returnOnEmpty = '#Array#'): mixed
    {
        if ($returnOnEmpty === '#Array#') {
            $returnOnEmpty = [
                'country-code' => null,
                'phone-number' => null,
            ];
        }
        if ($phoneNumber !== null) {
            $phoneNumber = trim($phoneNumber);
        }
        if (empty($phoneNumber)) {
            return $returnOnEmpty;
        }
        $countryCodes = CountryRepository::getPhoneNumberCountryCodes();

        for ($i = 1; $i < 4; $i++) {
            $countryCode = substr($phoneNumber, 0, ($i + 1));

            if (!isset($countryCodes[$countryCode])) {
                continue;
            }
            return [
                'country-code' => $countryCode,
                'phone-number' => substr($phoneNumber, ($i + 1)),
            ];
        }
        return $returnOnEmpty;

    } // explodePhoneNumber()


    /**
     *
     *
     * @param       string $number
     *
     * @param       string $defaultRegion
     *
     * @return      string|null
     *
     * @version     1.0.0 / 2026-05-10
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public static function formatPhoneNumber(string $number, string $defaultRegion = 'DE'): ?string
    {
        $PhoneUtil = PhoneNumberUtil::getInstance();

        try {
            /*
            **  Nummer parsen. */
            $parsed = $PhoneUtil->parse($number, strtoupper($defaultRegion));
            /*
            **  Gültigkeit prüfen. */
            if (!$PhoneUtil->isValidNumber($parsed)) {
                return null;
            }
            /*
            **  Internationales Format ausgeben: +49 151 12345678. */
            return $PhoneUtil->format($parsed, PhoneNumberFormat::INTERNATIONAL);

        } catch (NumberParseException $E) {
            return null;
        }
    } // formatPhoneNumber()


    /**
     *
     * @param       integer $length
     *
     * @param       string $pool
     *
     * @return      string
     *
     * @version     1.0.0 / 2026-05-10
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public static function getRandomChars(int $length, string $pool = self::DISTINCTIVE_LETTERS): string
    {
        $result = '';
        $max    = strlen($pool) - 1;

        for ($i = 0; $i < $length; $i++) {
            $result .= $pool[random_int(0, $max)];
        }
        return $result;

    } // getRandomChars()


    /**
     * Normalisiert die übergebene E-Mail-Adresse
     *
     * @param       null|string $email
     *
     * @param       mixed $returnOnEmpty
     *
     * @return 	    mixed
     *              Liefert die bereinigte E-Mail-Adresse in Kleinschreibung oder im Fehlerfall NULL
     *              beziehungsweise den übergebenen Return-Wert.
     *
     * @version     1.0.0 / 2026-05-10
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public static function normalizeEmail(null|string $email, mixed $returnOnEmpty = null): mixed
    {
        if ($email !== null) {
            $email = trim($email);
        }
        if (empty($email)) {
            return $returnOnEmpty;
        }
        return mb_strtolower($email);

    } // normalizeEmail()


    /**
     * Normalisiert das übergebene KFZ-Kennzeichen
     *
     * Entfernt alle nicht-alphanumerischen Zeichen aus der übergebenen Zeichenkette.
     *
     * @param       null|string $licensePlate
     *
     * @param       mixed $returnOnEmpty
     *
     * @return 	    mixed
     *              Liefert das bereinigte KFZ-Kennzeichen in Großschreibung oder im Fehlerfall NULL
     *              beziehungsweise den übergebenen Return-Wert.
     *
     * @version     1.0.0 / 2026-05-10
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public static function normalizeLicensePlate(null|string $licensePlate, mixed $returnOnEmpty = null): mixed
    {
        if ($licensePlate !== null) {
            $licensePlate = trim($licensePlate);
        }
        if (empty($licensePlate)) {
            return $returnOnEmpty;
        }
        /*
        **  Erklärung
        **  \p{L}   Erlaubt alle Buchstaben, einschließlich Umlauten und Buchstaben aus anderen Sprachen.
        **  \p{N}   Erlaubt alle Ziffern.
        **  ^       Negiert die Zeichenklasse, um alle Zeichen außer Buchstaben und Ziffern zu erfassen.
        **  +       Sorgt dafür, dass eine oder mehrere aufeinanderfolgende unzulässige Zeichen auf einmal entfernt werden.
        **  u       Aktiviert den UTF-8-Modus, damit auch Sonderzeichen korrekt verarbeitet werden. */
        return preg_replace('/[^\p{L}\p{N}]+/u', '', mb_strtoupper($licensePlate));

    } // normalizeLicensePlate()


    /**
     * Normalisiert einen gegebenen Pfad
     *
     * Im übergebenen Pfad werden Backslashes (\) durch Forwardslashes (/) ersetzt.
     * Außerdem werden abschließende Forwardslashes entfernt.
     *
     * @param       string $path
     *
     * @return      string
     *
     * @version     1.0.0 / 2026-05-10
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public static function normalizePath(string $path): string
    {
        $path = trim($path);

        if ($path === '') {
            return $path;
        }
        return rtrim(str_replace('\\', '/', $path), '/');

    } // normalizePath()


    /**
     * Normalisiert die übergebene Telefonnummer
     *
     * Entfernt alle nicht-numerischen Zeichen außer dem führenden Pluszeichen (+).
     *
     * @param       null|string $phoneNumber
     *              Erwartet eine vollständige Telefonnummer mit führendem Pluszeichen (+) oder
     *              vorangestellten Doppelnullen (00).
     *
     * @param       mixed $returnOnEmpty
     *
     * @return      mixed
     *              Liefert die bereinigte Telefonnummer mit führendem Pluszeichen oder im Fehlerfall
     *              NULL beziehungsweise den übergebenen Return-Wert.
     *
     * @version     1.0.0 / 2026-05-10
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public static function normalizePhoneNumber(null|string $phoneNumber, mixed $returnOnEmpty = null): mixed
    {
        if ($phoneNumber !== null) {
            $phoneNumber = trim($phoneNumber);
        }
        if (empty($phoneNumber)) {
            return $returnOnEmpty;
        }
        /*
        **  Entfernt alle nicht-numerischen Zeichen außer dem führenden Pluszeichen (+). */
        $normalized = preg_replace('/[^\d+]/', '', $phoneNumber);
        /*
        **  Führende Doppel-Nullen (00) werden durch ein Plussymbol (+) ersetzt. */
        if (strpos($normalized, '00') === 0) {
            $normalized = '+'.substr($normalized, 2);
        }
        return $normalized;

    } // normalizePhoneNumber()


    /**
     *
     * @param       string $string
     *
     * @param       CaseStyle|null $CaseStyle
     *              Steuert die Schreibweise des zurückgegebenen Werts.
     *              Beispiele anhand der Zeichenketten "deleted_at", "mainUser", "user-role":
     *
     *                  Parameter       Rückgaben
     *                  camel       >   deletedAt   /   mainUser    /   userRole
     *                  lower       >   deletedat   /   mainuser    /   user-role
     *                  slug        >   deleted-at  /   main-user   /   user-role
     *                  snake       >   deleted_at  /   main_user   /   user_role
     *                  studly      >   DeletedAt   /   MainUser    /   UserRole
     *                  upper       >   DELETED_AT  /   MAINUSER    /   USERROLE
     *
     * @param       Number|null $Number
     *              Der grammatikalische Numerus "plural" oder "singular".
     *
     * @return      string
     *
     * @version     1.0.0 / 2026-05-10
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public static function reformat(string $string, CaseStyle|null $CaseStyle = null, Number|null $Number = null): string
    {
        if ($Number !== null) {
            switch ($Number) {
                case Number::Plural:
                    $string = IlluminateStr::plural($string);
                    break;

                case Number::Singular:
                    $string = IlluminateStr::singular($string);
                    break;
            }
        }
        if ($CaseStyle !== null) {
            switch ($CaseStyle) {
                case CaseStyle::Camel:
                    $string = IlluminateStr::camel($string);
                    break;

                case CaseStyle::Lower:
                    $string = strtolower($string);
                    break;

                case CaseStyle::Slug:
                    $string = IlluminateStr::slug(self::reformat($string, CaseStyle::Snake), '-');
                    break;

                case CaseStyle::Snake:
                    $string = IlluminateStr::snake($string);
                    break;

                case CaseStyle::Studly:
                    $string = IlluminateStr::studly($string);
                    break;

                case CaseStyle::Upper:
                    $string = strtoupper($string);
                    break;
            }
        }
        return $string;

    } // reformat()


    /**
     * Entfernt einen BOM-Header (Byte Order Mark) vom Anfang der Zeichenkette
     * *
     * @param       string $string
     *
     * @return      string
     *
     * @version     1.0.0 / 2026-05-10
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public static function removeBomHeader(string $string): string
    {
        return preg_replace('/^\xEF\xBB\xBF/', '', $string);

    } // removeBomHeader()


    /**
     *
     * @param       string $string
     *
     * @param       string $lineBreak
     *              Der Zeilenumbruch, der verwendet werden soll (Standard: "\n").
     *
     * @param       bool $removeEmptyLines
     *              Standardmäßig werden doppelte Zeilenumbrüche entfernt, so dass
     *              das Ergebnis keine leeren Zeilen enthält. Mit diesem Parameter
     *              kann die Standardarbeitsweise deaktiviert werden.
     *
     * @return      string
     *
     * @version     1.0.0 / 2026-05-10
     * @author      Wassilios Meletiadis <wassilios.meletiadis@bplan-solutions.de>
     */
    public static function unifyLineBreaks(string $string, string $lineBreak = "\n", bool $removeEmptyLines = true): string
    {
        if (empty($string)) {
            return '';
        }
        /*
        **  Ersetzt alle Zeilenumbrüche durch den angegebenen Zeilenumbruch. */
        $string = preg_replace('/\r\n|\r|\n/', $lineBreak, $string);

        if ($removeEmptyLines === true) {
            /*
            **  Ersetzt alle doppelten Zeilenumbrüche, so dass leere Elemente/Zeilen
            **  entfernt werden. */
            $pattern = '/'.preg_quote($lineBreak, '/').'{2,}/';

            $string = preg_replace($pattern, $lineBreak, $string);
            /*
            **  Entfernt alle führenden und nachfolgenden Zeilenumbrüche. */
            $string = trim($string, $lineBreak);
        }
        return $string;

    } // unifyLineBreaks()


} // class Str {}
