<?php

/**
 * Хелпер для роботи з рядками
 * Розширені методи для маніпуляції рядками
 *
 * @package Flowaxy\Support\Helpers
 * @version 1.0.0 Alpha prerelease
 */

declare(strict_types=1);

namespace Flowaxy\Support\Helpers;

final class StringHelper
{
    /**
     * Конвертувати рядок в slug
     *
     * @param string $string
     * @param string $separator
     * @return string
     */
    public static function slug(string $string, string $separator = '-'): string
    {
        // Транслітерація кирилиці
        $transliteration = [
            'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd',
            'е' => 'e', 'ё' => 'yo', 'ж' => 'zh', 'з' => 'z', 'и' => 'i',
            'й' => 'y', 'к' => 'k', 'л' => 'l', 'м' => 'm', 'н' => 'n',
            'о' => 'o', 'п' => 'p', 'р' => 'r', 'с' => 's', 'т' => 't',
            'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'ts', 'ч' => 'ch',
            'ш' => 'sh', 'щ' => 'sch', 'ъ' => '', 'ы' => 'y', 'ь' => '',
            'э' => 'e', 'ю' => 'yu', 'я' => 'ya',
            'А' => 'A', 'Б' => 'B', 'В' => 'V', 'Г' => 'G', 'Д' => 'D',
            'Е' => 'E', 'Ё' => 'Yo', 'Ж' => 'Zh', 'З' => 'Z', 'И' => 'I',
            'Й' => 'Y', 'К' => 'K', 'Л' => 'L', 'М' => 'M', 'Н' => 'N',
            'О' => 'O', 'П' => 'P', 'Р' => 'R', 'С' => 'S', 'Т' => 'T',
            'У' => 'U', 'Ф' => 'F', 'Х' => 'H', 'Ц' => 'Ts', 'Ч' => 'Ch',
            'Ш' => 'Sh', 'Щ' => 'Sch', 'Ъ' => '', 'Ы' => 'Y', 'Ь' => '',
            'Э' => 'E', 'Ю' => 'Yu', 'Я' => 'Ya',
        ];

        $string = strtr($string, $transliteration);
        $string = mb_strtolower($string, 'UTF-8');
        $string = preg_replace('/[^a-z0-9]+/u', $separator, $string);
        $string = trim($string, $separator);

        return $string;
    }

    /**
     * Конвертувати рядок в camelCase
     *
     * @param string $string
     * @return string
     */
    public static function camel(string $string): string
    {
        $string = ucwords(str_replace(['-', '_', ' '], ' ', $string));

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

    /**
     * Конвертувати рядок в snake_case
     *
     * @param string $string
     * @return string
     */
    public static function snake(string $string): string
    {
        $string = preg_replace('/([a-z])([A-Z])/', '$1_$2', $string);
        $string = str_replace(['-', ' '], '_', $string);

        return mb_strtolower($string, 'UTF-8');
    }

    /**
     * Конвертувати рядок в kebab-case
     *
     * @param string $string
     * @return string
     */
    public static function kebab(string $string): string
    {
        return self::snake(str_replace('_', '-', $string));
    }

    /**
     * Конвертувати рядок в StudlyCase
     *
     * @param string $string
     * @return string
     */
    public static function studly(string $string): string
    {
        $string = ucwords(str_replace(['-', '_', ' '], ' ', $string));

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

    /**
     * Перевірити, чи починається рядок з підрядка
     *
     * @param string $haystack
     * @param string|array<int, string> $needles
     * @return bool
     */
    public static function startsWith(string $haystack, string|array $needles): bool
    {
        $needles = is_array($needles) ? $needles : [$needles];

        foreach ($needles as $needle) {
            if ($needle !== '' && str_starts_with($haystack, $needle)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Перевірити, чи закінчується рядок підрядком
     *
     * @param string $haystack
     * @param string|array<int, string> $needles
     * @return bool
     */
    public static function endsWith(string $haystack, string|array $needles): bool
    {
        $needles = is_array($needles) ? $needles : [$needles];

        foreach ($needles as $needle) {
            if ($needle !== '' && str_ends_with($haystack, $needle)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Перевірити, чи містить рядок підрядок
     *
     * @param string $haystack
     * @param string|array<int, string> $needles
     * @return bool
     */
    public static function contains(string $haystack, string|array $needles): bool
    {
        $needles = is_array($needles) ? $needles : [$needles];

        foreach ($needles as $needle) {
            if ($needle !== '' && str_contains($haystack, $needle)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Генерація випадкового рядка
     *
     * @param int $length
     * @return string
     */
    public static function random(int $length = 16): string
    {
        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $charactersLength = strlen($characters);
        $randomString = '';

        for ($i = 0; $i < $length; $i++) {
            $randomString .= $characters[random_int(0, $charactersLength - 1)];
        }

        return $randomString;
    }

    /**
     * Обмежити довжину рядка
     *
     * @param string $string
     * @param int $limit
     * @param string $end
     * @return string
     */
    public static function limit(string $string, int $limit = 100, string $end = '...'): string
    {
        if (mb_strlen($string, 'UTF-8') <= $limit) {
            return $string;
        }

        return rtrim(mb_substr($string, 0, $limit, 'UTF-8')) . $end;
    }

    /**
     * Обмежити кількість слів
     *
     * @param string $string
     * @param int $words
     * @param string $end
     * @return string
     */
    public static function words(string $string, int $words = 100, string $end = '...'): string
    {
        preg_match('/^\s*+(?:\S++\s*+){1,' . $words . '}/u', $string, $matches);

        if (!isset($matches[0]) || mb_strlen($string, 'UTF-8') === mb_strlen($matches[0], 'UTF-8')) {
            return $string;
        }

        return rtrim($matches[0]) . $end;
    }

    /**
     * Конвертувати в множину
     *
     * @param string $string
     * @return string
     */
    public static function plural(string $string): string
    {
        $rules = [
            '/(quiz)$/i' => '$1zes',
            '/^(ox)$/i' => '$1en',
            '/([m|l])ouse$/i' => '$1ice',
            '/(matr|vert|ind)ix|ex$/i' => '$1ices',
            '/(x|ch|ss|sh)$/i' => '$1es',
            '/([^aeiouy]|qu)y$/i' => '$1ies',
            '/(hive)$/i' => '$1s',
            '/(?:([^f])fe|([lr])f)$/i' => '$1$2ves',
            '/(shea|lea|loa|thie)f$/i' => '$1ves',
            '/sis$/i' => 'ses',
            '/([ti])um$/i' => '$1a',
            '/(tomat|potat|ech|her|vet)o$/i' => '$1oes',
            '/(bu)s$/i' => '$1ses',
            '/(alias)$/i' => '$1es',
            '/(octop)us$/i' => '$1i',
            '/(ax|test)is$/i' => '$1es',
            '/(us)$/i' => '$1es',
            '/s$/i' => 's',
            '/$/' => 's',
        ];

        foreach ($rules as $rule => $replacement) {
            if (preg_match($rule, $string)) {
                return preg_replace($rule, $replacement, $string);
            }
        }

        return $string . 's';
    }

    /**
     * Конвертувати в однину
     *
     * @param string $string
     * @return string
     */
    public static function singular(string $string): string
    {
        $rules = [
            '/(quiz)zes$/i' => '$1',
            '/(matr)ices$/i' => '$1ix',
            '/(vert|ind)ices$/i' => '$1ex',
            '/^(ox)en$/i' => '$1',
            '/(alias)es$/i' => '$1',
            '/(octop|vir)i$/i' => '$1us',
            '/(cris|ax|test)es$/i' => '$1is',
            '/(shoe)s$/i' => '$1',
            '/(o)es$/i' => '$1',
            '/(bus)es$/i' => '$1',
            '/([m|l])ice$/i' => '$1ouse',
            '/(x|ch|ss|sh)es$/i' => '$1',
            '/(m)ovies$/i' => '$1ovie',
            '/(s)eries$/i' => '$1eries',
            '/([^aeiouy]|qu)ies$/i' => '$1y',
            '/([lr])ves$/i' => '$1f',
            '/(tive)s$/i' => '$1',
            '/(hive)s$/i' => '$1',
            '/(li|wi|kni)ves$/i' => '$1fe',
            '/(shea|loa|lea|thie)ves$/i' => '$1f',
            '/(^analy)ses$/i' => '$1sis',
            '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '$1$2sis',
            '/([ti])a$/i' => '$1um',
            '/(n)ews$/i' => '$1ews',
            '/(h|bl)ouses$/i' => '$1ouse',
            '/(corpse)s$/i' => '$1',
            '/(us)es$/i' => '$1',
            '/s$/i' => '',
        ];

        foreach ($rules as $rule => $replacement) {
            if (preg_match($rule, $string)) {
                return preg_replace($rule, $replacement, $string);
            }
        }

        return $string;
    }

    /**
     * Конвертувати першу літеру кожного слова в верхній регістр
     *
     * @param string $string
     * @return string
     */
    public static function title(string $string): string
    {
        return mb_convert_case($string, MB_CASE_TITLE, 'UTF-8');
    }

    /**
     * Конвертувати в верхній регістр
     *
     * @param string $string
     * @return string
     */
    public static function upper(string $string): string
    {
        return mb_strtoupper($string, 'UTF-8');
    }

    /**
     * Конвертувати в нижній регістр
     *
     * @param string $string
     * @return string
     */
    public static function lower(string $string): string
    {
        return mb_strtolower($string, 'UTF-8');
    }

    /**
     * Конвертувати першу літеру в верхній регістр
     *
     * @param string $string
     * @return string
     */
    public static function ucfirst(string $string): string
    {
        return mb_strtoupper(mb_substr($string, 0, 1, 'UTF-8'), 'UTF-8') . mb_substr($string, 1, null, 'UTF-8');
    }

    /**
     * Конвертувати першу літеру в нижній регістр
     *
     * @param string $string
     * @return string
     */
    public static function lcfirst(string $string): string
    {
        return mb_strtolower(mb_substr($string, 0, 1, 'UTF-8'), 'UTF-8') . mb_substr($string, 1, null, 'UTF-8');
    }

    /**
     * Замінити всі входження
     *
     * @param string|array<int, string> $search
     * @param string|array<int, string> $replace
     * @param string $subject
     * @return string
     */
    public static function replace(string|array $search, string|array $replace, string $subject): string
    {
        return str_replace($search, $replace, $subject);
    }

    /**
     * Замінити перше входження
     *
     * @param string $search
     * @param string $replace
     * @param string $subject
     * @return string
     */
    public static function replaceFirst(string $search, string $replace, string $subject): string
    {
        if ($search === '') {
            return $subject;
        }

        $position = strpos($subject, $search);

        if ($position !== false) {
            return substr_replace($subject, $replace, $position, strlen($search));
        }

        return $subject;
    }

    /**
     * Замінити останнє входження
     *
     * @param string $search
     * @param string $replace
     * @param string $subject
     * @return string
     */
    public static function replaceLast(string $search, string $replace, string $subject): string
    {
        if ($search === '') {
            return $subject;
        }

        $position = strrpos($subject, $search);

        if ($position !== false) {
            return substr_replace($subject, $replace, $position, strlen($search));
        }

        return $subject;
    }

    /**
     * Маскувати рядок
     *
     * @param string $string
     * @param string $character
     * @param int $index
     * @param int|null $length
     * @return string
     */
    public static function mask(string $string, string $character = '*', int $index = 0, ?int $length = null): string
    {
        if ($length === null) {
            $length = strlen($string) - $index;
        }

        $start = max(0, $index);
        $end = min(strlen($string), $start + $length);

        return substr($string, 0, $start) . str_repeat($character, $end - $start) . substr($string, $end);
    }

    /**
     * Доповнити рядок до вказаної довжини
     *
     * @param string $string
     * @param int $length
     * @param string $padString
     * @param int $padType
     * @return string
     */
    public static function pad(string $string, int $length, string $padString = ' ', int $padType = STR_PAD_RIGHT): string
    {
        return str_pad($string, $length, $padString, $padType);
    }

    /**
     * Видалити пробіли з початку та кінця
     *
     * @param string $string
     * @param string $characters
     * @return string
     */
    public static function trim(string $string, string $characters = " \t\n\r\0\x0B"): string
    {
        return trim($string, $characters);
    }

    /**
     * Видалити пробіли з початку
     *
     * @param string $string
     * @param string $characters
     * @return string
     */
    public static function ltrim(string $string, string $characters = " \t\n\r\0\x0B"): string
    {
        return ltrim($string, $characters);
    }

    /**
     * Видалити пробіли з кінця
     *
     * @param string $string
     * @param string $characters
     * @return string
     */
    public static function rtrim(string $string, string $characters = " \t\n\r\0\x0B"): string
    {
        return rtrim($string, $characters);
    }
}
