ToolsHelper

<?php

namespace Helper;

use CIBlockFormatProperties;
use CSite;
use Bitrix\Main\Application;
use Bitrix\Main\IO;

class ToolsHelper
{
    /**
     * Упорядочиваем массив по значению ключа.
     * По возрастанию.
     */
    public static function orderArrayByKey(array $array, string $key): array
    {
        usort($array, static function ($a, $b) use ($key) {
            return $a[$key] <=> $b[$key];
        });

        return $array ?? [];
    }

    /**
     * Разбивает массив на более мелкие массивы с количеством элементов, указанных в $pieces, вида [3, 3, 5, 4].
     */

    public static function splitArray(array $items, array $pieces): array
    {
        $result = [];

        foreach ($pieces as $length) {
            $result[] = array_slice($items, 0, $length);
            $items = array_slice($items, $length);
        }

        if (!empty($items)) {
            $result[] = $items;
        }

        return $result;
    }

    /**
     * Возвращает дату в формате "27 февраля 2007"
     */
    public static function getDateCreate(string $dateCreate): string
    {
        return CIBlockFormatProperties::DateFormat('j F Y', MakeTimeStamp($dateCreate, CSite::GetDateFormat()));
    }

    /**
     * Проверяем длину строки. Получаем значение референс, сравниваем по нему.
     */
    public static function stringLengthMoreThan(string $string, int $length = 150): bool
    {
        return strlen($string) > $length;
    }

    /**
     * Проверяем ведет ли ссылка на внешний ресурс.
     */
    public static function isExtLink(string $url): bool
    {
        $parsedUrl = parse_url($url);

        // Если есть схема и это 'http' или 'https'.
        return isset($parsedUrl['scheme']) && in_array($parsedUrl['scheme'], ['http', 'https']);
    }

    /**
     * Проверяем является ли файл локальным. По наличию upload в пути к файлу.
     */
    public static function isLocalFile(string $path): bool
    {
        return strpos($path, 'upload') && file_exists($_SERVER['DOCUMENT_ROOT'] . $path);
    }

    /**
     * Возвращаем расширение файла в строковом представлении.
     */
    public static function getFileExtension(int $fileId = 0, string $path = ''): string
    {
        $filePath = $fileId !== 0 ? \CFile::GetPath($fileId) : $path;
        if ($filePath !== '') {
            if (self::isExtLink($filePath)) {
                return 'ext';
            }
            $file = new IO\File(Application::getDocumentRoot() . $filePath);
            $extension = $file->getExtension();
        }

        return $extension ?? '';
    }

    /**
     * Валидация E-mail.
     */
    public static function validationEmail($email): bool
    {
        return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
    }

    /**
     * Удаление из строки всех ненужных символов.
     */
    public static function cleanString(string $string, string $patternKey = '', array $pattern = []): string
    {
        if (empty($pattern) && empty($patternKey)) {
            return '';
        }

        $patterns = [
            'phone' => ['+7', ' ', '(', ')', '-'],
        ];

        $input = !empty($patternKey) && isset($patterns[$patternKey]) ? $patterns[$patternKey] : $pattern;

        return str_replace($input, '', $string);
    }

    /**
     * Удаляем из строки теги, пробелы, переносы.
     */
    public static function cleanStringTags($input): string
    {
        // Удаляем все HTML-теги.
        $cleaned = strip_tags($input);

        // Удаляем &nbsp; и заменяем его на обычный пробел.
        $cleaned = str_replace('&nbsp;', ' ', $cleaned);

        // Удаляем переносы строк и лишние пробелы.
        $cleaned = preg_replace('/\s+/', ' ', $cleaned);

        return trim($cleaned);
    }

    /**
     * Форматируем номер телефона с удалением лишних символов для вставки в ссылку.
     * Добавляем добавочный номер по разделителю "_".
     * Из строки (8-38553) 25-3-01_2 делаем:
     * 1) 83855325301,2 - formatPhoneForTel()
     * 2) (8-38553) 25-3-01 доб.2 - formatPhoneForDisplay()
     */
    public static function formatPhoneForTel($phone): string
    {
        // Убираем пробелы и заменяем символ "_".
        $phone = str_replace('_', ',', $phone);

        // Удаляем символы, которые не являются цифрами, кроме запятой.
        return preg_replace('/[^\d,]/', '', $phone);
    }

    public static function formatPhoneForDisplay($phone): string
    {
        return str_replace('_', ' доб.', $phone);
    }

    /**
     * Считаем кол-во полных лет Пользователя. Принимаем строку/объект с датой.
     */
    public static function calculateAge(string | object $birthDate): int
    {
        // Преобразуем дату в формат DateTime.
        $dateOfBirth = \DateTime::createFromFormat('d.m.Y', $birthDate);

        // Проверяем, была ли дата корректно создана.
        if (!$dateOfBirth) {
            return 0;
        }

        // Получаем текущую дату
        $currentDate = new \DateTime();

        // Вычисляем разницу
        return $currentDate->diff($dateOfBirth)->y;
    }

    /**
     * Окончание и форма слова "лет".
     */
    public static function getYearsText(int $age): string
    {
        if ($age < 0) {
            return "Некорректный возраст";
        }

        if ($age % 10 === 1 && $age % 100 !== 11) {
            return "$age год";
        }

        if ($age % 10 >= 2 && $age % 10 <= 4 && ($age % 100 < 10 || $age % 100 >= 20)) {
            return "$age года";
        }

        return "$age лет";
    }

    /**
     * Окончание и форма слова "лет".
     */
    public static function getProductsText(int $countProducts): string
    {
        if ($countProducts < 0) {
            return "Некорректное количество";
        }

        if ($countProducts % 10 === 1 && $countProducts % 100 !== 11) {
            return "$countProducts товар";
        }

        if (
            $countProducts % 10 >= 2 && $countProducts % 10 <= 4
            && ($countProducts % 100 < 10
                || $countProducts % 100
                >= 20)
        ) {
            return "$countProducts товара";
        }

        return "$countProducts товаров";
    }

    /**
     * Приводим число к виду двух знаков после точки.
     */
    public static function formatNumber($number): string
    {
        return number_format($number, 2, '.', '');
    }

    /**
     * Для catalog.section.list
     * Собираем древовидную структуру из плоского массива.
     *
     * Ожидаем наличие 'IBLOCK_SECTION_ID' внутри.
     */
    public static function buildCategoriesTree(array $sections): array
    {
        $categories = [];
        $sectionsMap = [];

        // Сначала создаём временный массив для быстрого доступа к категориям по ID.
        foreach ($sections as $section) {
            $sectionsMap[$section['ID']] = [
                'ID' => $section['ID'],
                'NAME' => $section['NAME'],
                'SECTION_PAGE_URL' => $section['SECTION_PAGE_URL'],
                'ELEMENT_CNT' => $section['ELEMENT_CNT'],
                'RELATIVE_DEPTH_LEVEL' => $section['RELATIVE_DEPTH_LEVEL'],
                'CHILDREN' => [],
            ];
        }

        // Строим дерево.
        foreach ($sections as $section) {
            // Если есть родитель.
            if (
                isset($section['IBLOCK_SECTION_ID'])
                && $section['IBLOCK_SECTION_ID'] > 0
                && isset($sectionsMap[$section['IBLOCK_SECTION_ID']])
            ) {
                // Добавляем текущую категорию к дочерним элементам родителя.
                $sectionsMap[$section['IBLOCK_SECTION_ID']]['CHILDREN'][] = &$sectionsMap[$section['ID']];
            } else {
                // Если родителя нет, это корневая категория.
                $categories[] = &$sectionsMap[$section['ID']];
            }
        }

        return $categories;
    }

    /**
     * Универсальная функция для группировки свойств по родительским элементам.
     * Например, свойства тип "Список", полученные GetList-ом.
     *
     * Пример вызова:
     *
     * $fieldsToGroup = [
     *      'STEP_LIST_VALUE' => 'STEP_LIST_VALUES',
     *      'STEP_EQUIPMENT_VALUE' => 'STEP_EQUIPMENT_VALUES'
     * ];
     *
     * $arResult['ABOUT_SYSTEM'] = ToolsHelper::groupPropertiesByParent($rawData, $fieldsToGroup);
     *
     * @param array $data Исходный массив данных.
     * @param array $groupKeys Ключи полей для группировки (формат: ['FIELD_NAME' => 'GROUPED_ARRAY_NAME']).
     * @param string $idField Поле, которое используется как идентификатор родителя (по умолчанию 'ID').
     * @param string $nameField Поле, которое используется как имя родителя (по умолчанию 'NAME').
     *
     * @return array Сгруппированный массив с родительскими элементами и их свойствами.
     */

    public static function groupPropertiesByParent(
        array $data,
        array $groupKeys = [],
        string $idField = 'ID',
        string $nameField = 'NAME'
    ): array {
        $result = [];

        if (empty($groupKeys)) {
            return ['error' => '$groupKeys пустой'];
        }

        foreach ($data as $item) {
            $parentId = $item[$idField];
            $parentName = $item[$nameField];

            // Если такого родителя еще нет в результирующем массиве - создаем его.
            if (!isset($result[$parentId])) {
                // Копируем все исходные поля элемента.
                $result[$parentId] = $item;

                // Инициализируем массивы для группировки.
                foreach ($groupKeys as $sourceKey => $groupedArrayName) {
                    $result[$parentId][$groupedArrayName] = [];
                }
            }

            // Заполняем массивы сгруппированных значений.
            foreach ($groupKeys as $sourceKey => $groupedArrayName) {
                if (isset($item[$sourceKey]) && !empty($item[$sourceKey]) &&
                    !in_array($item[$sourceKey], $result[$parentId][$groupedArrayName])) {
                    $result[$parentId][$groupedArrayName][] = $item[$sourceKey];
                }
            }

            // Обновляем остальные поля, если они не пустые в текущем элементе, но пустые в результирующем.
            foreach ($item as $key => $value) {
                if (!in_array($key, array_keys($groupKeys)) && $key !== $idField && $key !== $nameField) {
                    if (!empty($value) && (empty($result[$parentId][$key]) || $result[$parentId][$key] === null)) {
                        $result[$parentId][$key] = $value;
                    }
                }
            }
        }

        // Преобразуем ассоциативный массив в индексированный.
        return array_values($result);
    }

    /**
     * Рассчитывает время чтения текста
     *
     * @param string $text Исходный текст статьи
     * @param int $wordsPerMinute Скорость чтения (слов в минуту)
     *
     * @return string Отформатированная строка с временем чтения
     *
     * // Пример использования
     * $articleContent = '<h1>Заголовок статьи</h1><p>Это пример содержания вашей статьи...</p>';
     * $readingTime = calculateReadingTime($articleContent);
     *
     * Echo "Время чтения: {$readingTime}";
     */
    public static function calculateReadingTime(string $text, int $wordsPerMinute = 200): string {
        // Удаляем HTML-теги и лишние пробелы
        $cleanText = trim(strip_tags($text));

        // Если текст пустой
        if (empty($cleanText)) {
            return "меньше минуты";
        }

        // Подсчет слов (учитываем кириллицу, латиницу и цифры)
        $words = preg_split('/\s+/u', $cleanText, -1, PREG_SPLIT_NO_EMPTY);
        $wordCount = count($words);

        // Расчет минут
        $minutes = (int) max(1, ceil($wordCount / $wordsPerMinute));

        // Склонение слова "минута"
        /*$lastDigit = $minutes % 10;
        $lastTwoDigits = $minutes % 100;

        if ($lastTwoDigits >= 11 && $lastTwoDigits <= 19) {
            $form = 'минут';
        } elseif ($lastDigit === 1) {
            $form = 'минута';
        } elseif ($lastDigit >= 2 && $lastDigit <= 4) {
            $form = 'минуты';
        } else {
            $form = 'минут';
        }*/

        // Или
        $form = 'мин';

        return "{$minutes} {$form}";
    }

    /**
     * Обрезаем строку по заданному разделителю.
     */
    public static function cutStringByDelimiter(string $str, string $delimiter): string
    {
        $pos = strpos($str, $delimiter);

        if ($pos === false) {
            // Разделитель не найден - возвращаем исходную строку
            return $str;
        }

        // Обрезаем до разделителя (исключая сам разделитель)
        return substr($str, 0, $pos);
    }

}