<?php
/**
 * BookiFlex Core
 *
 * @package    BookiFlex
 * @subpackage Core
 * @author     BookiFlex
 * @copyright  Copyright (c) 2025 BookiFlex
 * @license    GPL-2.0-or-later
 *
 * @link       https://bookiflex.com
 */

declare(strict_types=1);

namespace BookiFlex\Core\Util;

defined( 'ABSPATH' ) || exit;

/**
 * Generates short, meaningful codes (3-5 uppercase Latin chars + digits) from text.
 *
 * Examples:
 * - "Standard Room" → "STDR"
 * - "Deluxe" → "DLX"
 * - "Pokój Deluxe" → "PKDLX"
 * - "Люкс Премиум" → "LXPRM"
 *
 * Features:
 * - Transliterates Cyrillic to Latin
 * - Normalizes diacritics (ą→a, ć→c, é→e, etc.)
 * - Uses smart abbreviation (consonant-based)
 * - Ensures uniqueness with numeric suffix
 * - No external PHP extensions required
 */
class CodeGenerator
{
    private const MIN_LENGTH = 3;
    private const MAX_LENGTH = 5;

    /**
     * Cyrillic to Latin transliteration map
     */
    private const CYRILLIC_MAP = [
        'А' => 'A',  'а' => 'a',
        'Б' => 'B',  'б' => 'b',
        'В' => 'V',  'в' => 'v',
        'Г' => 'G',  'г' => 'g',
        'Д' => 'D',  'д' => 'd',
        'Е' => 'E',  'е' => 'e',
        'Ё' => 'E',  'ё' => 'e',
        'Ж' => 'ZH', 'ж' => 'zh',
        'З' => 'Z',  'з' => 'z',
        'И' => 'I',  'и' => 'i',
        'Й' => 'Y',  'й' => 'y',
        'К' => 'K',  'к' => 'k',
        'Л' => 'L',  'л' => 'l',
        'М' => 'M',  'м' => 'm',
        'Н' => 'N',  'н' => 'n',
        'О' => 'O',  'о' => 'o',
        'П' => 'P',  'п' => 'p',
        'Р' => 'R',  'р' => 'r',
        'С' => 'S',  'с' => 's',
        'Т' => 'T',  'т' => 't',
        'У' => 'U',  'у' => 'u',
        'Ф' => 'F',  'ф' => 'f',
        'Х' => 'H',  'х' => 'h',
        'Ц' => 'TS', 'ц' => 'ts',
        'Ч' => 'CH', 'ч' => 'ch',
        'Ш' => 'SH', 'ш' => 'sh',
        'Щ' => 'SCH','щ' => 'sch',
        'Ъ' => '',   'ъ' => '',
        'Ы' => 'Y',  'ы' => 'y',
        'Ь' => '',   'ь' => '',
        'Э' => 'E',  'э' => 'e',
        'Ю' => 'YU', 'ю' => 'yu',
        'Я' => 'YA', 'я' => 'ya',
        // Ukrainian
        'Є' => 'YE', 'є' => 'ye',
        'І' => 'I',  'і' => 'i',
        'Ї' => 'YI', 'ї' => 'yi',
        'Ґ' => 'G',  'ґ' => 'g',
    ];

    /**
     * Diacritic to ASCII map (Polish, Czech, German, French, Spanish, etc.)
     */
    private const DIACRITIC_MAP = [
        // Polish
        'Ą' => 'A', 'ą' => 'a',
        'Ć' => 'C', 'ć' => 'c',
        'Ę' => 'E', 'ę' => 'e',
        'Ł' => 'L', 'ł' => 'l',
        'Ń' => 'N', 'ń' => 'n',
        'Ó' => 'O', 'ó' => 'o',
        'Ś' => 'S', 'ś' => 's',
        'Ź' => 'Z', 'ź' => 'z',
        'Ż' => 'Z', 'ż' => 'z',
        // Czech/Slovak
        'Č' => 'C', 'č' => 'c',
        'Ď' => 'D', 'ď' => 'd',
        'Ě' => 'E', 'ě' => 'e',
        'Ň' => 'N', 'ň' => 'n',
        'Ř' => 'R', 'ř' => 'r',
        'Š' => 'S', 'š' => 's',
        'Ť' => 'T', 'ť' => 't',
        'Ů' => 'U', 'ů' => 'u',
        'Ž' => 'Z', 'ž' => 'z',
        // German
        'Ä' => 'A', 'ä' => 'a',
        'Ö' => 'O', 'ö' => 'o',
        'Ü' => 'U', 'ü' => 'u',
        'ß' => 'ss',
        // French
        'À' => 'A', 'à' => 'a',
        'Â' => 'A', 'â' => 'a',
        'Æ' => 'AE','æ' => 'ae',
        'Ç' => 'C', 'ç' => 'c',
        'È' => 'E', 'è' => 'e',
        'É' => 'E', 'é' => 'e',
        'Ê' => 'E', 'ê' => 'e',
        'Ë' => 'E', 'ë' => 'e',
        'Î' => 'I', 'î' => 'i',
        'Ï' => 'I', 'ï' => 'i',
        'Ô' => 'O', 'ô' => 'o',
        'Œ' => 'OE','œ' => 'oe',
        'Ù' => 'U', 'ù' => 'u',
        'Û' => 'U', 'û' => 'u',
        'Ÿ' => 'Y', 'ÿ' => 'y',
        // Spanish
        'Á' => 'A', 'á' => 'a',
        'Í' => 'I', 'í' => 'i',
        'Ñ' => 'N', 'ñ' => 'n',
        'Ú' => 'U', 'ú' => 'u',
        // Italian
        'Ì' => 'I', 'ì' => 'i',
        'Ò' => 'O', 'ò' => 'o',
        // Portuguese
        'Ã' => 'A', 'ã' => 'a',
        'Õ' => 'O', 'õ' => 'o',
        // Nordic
        'Å' => 'A', 'å' => 'a',
        'Ø' => 'O', 'ø' => 'o',
    ];

    /**
     * Vowels to remove for abbreviation
     */
    private const VOWELS = ['A', 'E', 'I', 'O', 'U', 'Y'];

    /**
     * Generate a unique code from input text
     *
     * @param string $input Text to generate code from
     * @param array<string> $existingCodes Already existing codes to avoid collisions
     * @return string Generated code (3-5 uppercase chars/digits)
     */
    public function generate(string $input, array $existingCodes = []): string
    {
        $normalized = $this->normalize($input);
        $baseCode = $this->generateBaseCode($normalized);
        $finalCode = $this->ensureUniqueness($baseCode, $existingCodes);

        return $finalCode;
    }

    /**
     * Normalize input: transliterate Cyrillic, remove diacritics, clean special chars
     */
    private function normalize(string $input): string
    {
        // Step 1: Transliterate Cyrillic
        $text = strtr($input, self::CYRILLIC_MAP);

        // Step 2: Normalize diacritics
        $text = strtr($text, self::DIACRITIC_MAP);

        // Step 3: Remove non-alphanumeric except spaces
        $text = preg_replace('/[^a-zA-Z0-9\s]/', '', $text);

        // Step 4: Normalize whitespace
        $text = preg_replace('/\s+/', ' ', trim($text));

        return $text;
    }

    /**
     * Generate base code using smart abbreviation algorithm
     */
    private function generateBaseCode(string $normalized): string
    {
        $words = explode(' ', $normalized);

        // Multiple words: use first letters + consonants
        if (count($words) > 1) {
            return $this->generateFromMultipleWords($words);
        }

        // Single word: remove vowels
        return $this->generateFromSingleWord($words[0]);
    }

    /**
     * Generate code from multiple words (first letters strategy)
     */
    private function generateFromMultipleWords(array $words): string
    {
        $code = '';

        foreach ($words as $word) {
            if ($word === '') {
                continue;
            }

            $upper = mb_strtoupper($word);
            // Take first letter
            $code .= mb_substr($upper, 0, 1);

            // Add consonants from the rest if code is still short
            if (mb_strlen($code) < self::MAX_LENGTH && mb_strlen($upper) > 1) {
                $rest = mb_substr($upper, 1);
                $consonants = $this->removeVowels($rest);
                $code .= mb_substr($consonants, 0, self::MAX_LENGTH - mb_strlen($code));
            }
        }

        return $this->normalizeLength($code);
    }

    /**
     * Generate code from single word (consonant strategy)
     */
    private function generateFromSingleWord(string $word): string
    {
        $upper = mb_strtoupper($word);

        // Try removing vowels first
        $withoutVowels = $this->removeVowels($upper);

        // If we got a good length, use it
        if (mb_strlen($withoutVowels) >= self::MIN_LENGTH) {
            return $this->normalizeLength($withoutVowels);
        }

        // Otherwise, use original word
        return $this->normalizeLength($upper);
    }

    /**
     * Remove vowels from text
     */
    private function removeVowels(string $text): string
    {
        $result = '';
        $length = mb_strlen($text);

        for ($i = 0; $i < $length; $i++) {
            $char = mb_substr($text, $i, 1);
            if (!in_array($char, self::VOWELS, true)) {
                $result .= $char;
            }
        }

        return $result;
    }

    /**
     * Normalize code length to 3-5 characters
     */
    private function normalizeLength(string $code): string
    {
        $length = mb_strlen($code);

        // Too short: can't do much, return as is (will be handled by caller)
        if ($length < self::MIN_LENGTH) {
            // Pad with first chars repeated if absolutely needed
            while (mb_strlen($code) < self::MIN_LENGTH && mb_strlen($code) > 0) {
                $code .= mb_substr($code, 0, 1);
            }
            return $code;
        }

        // Too long: truncate to MAX_LENGTH
        if ($length > self::MAX_LENGTH) {
            return mb_substr($code, 0, self::MAX_LENGTH);
        }

        // Perfect length
        return $code;
    }

    /**
     * Ensure code uniqueness by adding numeric suffix
     */
    private function ensureUniqueness(string $baseCode, array $existingCodes): string
    {
        $code = $baseCode;
        $counter = 2;

        // Check if base code exists
        while (in_array($code, $existingCodes, true)) {
            // Add numeric suffix, ensuring we stay within MAX_LENGTH
            $suffix = (string)$counter;
            $maxBaseLength = self::MAX_LENGTH - mb_strlen($suffix);

            $code = mb_substr($baseCode, 0, $maxBaseLength) . $suffix;
            $counter++;

            // Safety: prevent infinite loop
            if ($counter > 999) {
                break;
            }
        }

        return $code;
    }
}
