<?php

namespace NoahVet\Reef\Domain\Tool;

class StringTool implements StringToolInterface
{
    #[\Override]
    public static function fromSnakeCaseToCamelCase(string $subject): string
    {
        return \lcfirst(
            \str_replace(
                ' ',
                '',
                \ucwords(
                    \str_replace(
                        '_',
                        ' ',
                        \strtolower(
                            \trim($subject),
                        ),
                    ),
                ),
            ),
        );
    }

    #[\Override]
    public static function fromBurgerCaseToSnakeCase(string $subject): string
    {
        return \mb_strtolower(
            \str_replace(
                '-',
                '_',
                \trim($subject),
            ),
        );
    }

    #[\Override]
    public static function fromSnakeCaseToBurgerCase(string $subject): string
    {
        return \mb_strtolower(
            \str_replace(
                '_',
                '-',
                \trim($subject),
            ),
        );
    }

    #[\Override]
    public static function fromPascalCaseToCamelCase(string $subject): string
    {
        return \lcfirst(\trim($subject));
    }

    #[\Override]
    public static function fromSpacedToBurgerCase(string $subject): string
    {
        return \mb_strtolower(
            \str_replace(
                ' ',
                '-',
                \trim($subject),
            ),
        );
    }

    #[\Override]
    public static function fromSpacedToUpperBurgerCase(string $subject): string
    {
        return \mb_strtoupper(
            \str_replace(
                ' ',
                '-',
                \trim($subject),
            ),
        );
    }

    #[\Override]
    public static function fromSpacedToPascalCase(string $subject): string
    {
        return \ucwords(
            \mb_strtolower(
                \trim($subject),
            ),
        );
    }

    #[\Override]
    public static function toPascalCase(string $subject): string
    {
        $subject = \trim($subject);

        /** @var list<string>|false $parts */
        $parts = \preg_split(
            '/(?<=\p{Ll})(?=\p{Lu})|[^[:alnum:]]+/u',
            $subject,
            -1,
            \PREG_SPLIT_NO_EMPTY,
        );

        if (false === $parts) {
            $parts = [];
        }

        $parts = \array_map(static function (string $w): string {
            if (\preg_match('/^\p{Lu}{2,}\d*$/u', $w)) {
                return $w;
            }
            if (\preg_match('/^\p{Lu}\p{Lu}+/u', $w)) {
                return $w;
            }
            $w = \mb_strtolower($w, 'UTF-8');
            $first = \mb_substr($w, 0, 1, 'UTF-8');
            $rest = \mb_substr($w, 1, null, 'UTF-8');

            return \mb_strtoupper($first, 'UTF-8').$rest;
        }, $parts);

        return \implode('', $parts);
    }

    #[\Override]
    public static function fromSpacedToSnakeUpperCase(string $subject): string
    {
        return \mb_strtoupper(
            \str_replace(
                ' ',
                '_',
                \trim($subject),
            ),
        );
    }

    #[\Override]
    public static function toSnakeUpperCase(string $subject): string
    {
        $burgerCase = self::fromCamelCaseToBurgerCase($subject);

        if ('' === $burgerCase) {
            return '';
        }

        return \mb_strtoupper(
            \str_replace(
                '-',
                '_',
                $burgerCase,
            ),
            'UTF-8',
        );
    }

    #[\Override]
    public static function toLowerCaseWithoutAccent(string $subject): string
    {
        return self::removeAccent(
            \mb_strtolower($subject),
        );
    }

    #[\Override]
    public static function toUpperCaseWithoutAccent(string $subject): string
    {
        return self::removeAccent(
            \mb_strtoupper($subject),
        );
    }

    #[\Override]
    public static function removeAccent(string $subject): string
    {
        return \str_replace(
            \mb_str_split(self::CHAR_LIST_WITH_ACCENT),
            \mb_str_split(self::LOWER_CASE_LIST_WITH_ACCENT.self::UPPER_CASE_LIST_WITH_ACCENT),
            \trim($subject),
        );
    }

    #[\Override]
    public static function removeWhitespace(string $subject): string
    {
        return \str_replace(
            ' ',
            '',
            $subject,
        );
    }

    #[\Override]
    public static function toName(string $name, array $supportedSeparators = self::SUPPORTED_SEPARATORS): string
    {
        $name = \ucwords(\mb_strtolower(\trim($name)));

        foreach ($supportedSeparators as $separator) {
            if (\str_contains($name, $separator) && !empty($separator)) {
                $namePart = \explode($separator, $name);
                $name = \implode(
                    $separator,
                    \array_map(
                        'ucfirst',
                        \array_map(
                            'mb_strtolower',
                            $namePart,
                        ),
                    ),
                );
            }
        }

        return $name;
    }

    /**
     * Convert camelCase/PascalCase to burger-case while preserving all-uppercase acronyms.
     *
     * Examples:
     *  - "camelCase"        -> "camel-case"
     *  - "PascalCase"       -> "pascal-case"
     *  - "HTTPResponse2XX"  -> "HTTP-response-2-XX"
     *  - " aABáâCãDä "      -> "a-A-báâ-cã-dä"
     */
    #[\Override]
    public static function fromCamelCaseToBurgerCase(string $subject): string
    {
        // Split acronyms followed by Cap+lower sequences: "HTTPResponse" -> "HTTP-Response"
        $s = self::safePregReplace('/(\p{Lu}+)(\p{Lu}\p{Ll})/u', '$1-$2', $subject);

        // Insert hyphen on lower/digit -> upper transitions: "camelCase" -> "camel-Case", "ver2X" -> "ver-2-X"
        $s = self::safePregReplace('/(\p{Ll}|\p{Nd})(\p{Lu})/u', '$1-$2', $s);

        // Insert hyphen on letter <-> digit transitions: "foo2Bar" -> "foo-2-Bar", "foo2bar" -> "foo-2bar"
        $s = self::safePregReplace('/(\p{L})(\p{Nd})/u', '$1-$2', $s);
        $s = self::safePregReplace('/(\p{Nd})(\p{L})/u', '$1-$2', $s);

        // Normalize any non-alphanumeric separators to a single hyphen
        $s = self::safePregReplace('/[^\p{L}\p{Nd}]+/u', '-', $s);

        // Trim leading/trailing hyphens
        $s = \trim($s, '-');

        if ('' === $s) {
            return '';
        }

        // Split into segments; ensure an array for static analyzers
        $parts = \preg_split('/-+/u', $s, -1, \PREG_SPLIT_NO_EMPTY);
        if (false === $parts) {
            $parts = [$s];
        }

        // Lowercase only segments that are not full-uppercase (preserve "A", "ID", "HTTP")
        foreach ($parts as $i => $p) {
            if (!\preg_match('/^\p{Lu}+$/u', $p)) {
                $parts[$i] = \mb_strtolower($p, 'UTF-8');
            }
        }

        return \implode('-', $parts);
    }

    /**
     * Safe wrapper around preg_replace that always returns a string.
     *
     * @psalm-param non-empty-string $pattern
     */
    protected static function safePregReplace(string $pattern, string $replacement, string $subject): string
    {
        // If the pattern is invalid (including empty), preg_replace returns null.
        $result = \preg_replace($pattern, $replacement, $subject);

        if (!\is_string($result)) {
            throw new \InvalidArgumentException('Invalid PCRE pattern passed to preg_replace.');
        }

        return $result;
    }
}
