<?php

namespace App\Helpers;

use App\Models\Setting;

class NumberHelper
{
    public static function formatAmount(float $amount, int $companyId, bool $withSymbol = false): string
    {
        $decimals = (int) Setting::get($companyId, 'amount_decimals', 2);

        $formatted = self::usesBangladeshiGrouping($companyId)
            ? self::formatWithBangladeshiGrouping($amount, $decimals)
            : number_format($amount, $decimals, '.', self::thousandSeparator($companyId));

        if ($withSymbol) {
            $symbol = Setting::get($companyId, 'currency_symbol', '$');

            return $symbol . $formatted;
        }

        return $formatted;
    }

    public static function formatQty(float $qty, int $companyId): string
    {
        $decimals = (int) Setting::get($companyId, 'qty_decimals', 2);

        if (self::usesBangladeshiGrouping($companyId)) {
            return self::formatWithBangladeshiGrouping($qty, $decimals);
        }

        return number_format($qty, $decimals, '.', self::thousandSeparator($companyId));
    }

    public static function formatRate(float $rate, int $companyId): string
    {
        $decimals = (int) Setting::get($companyId, 'rate_decimals', 4);

        if (self::usesBangladeshiGrouping($companyId)) {
            return self::formatWithBangladeshiGrouping($rate, $decimals);
        }

        return number_format($rate, $decimals, '.', self::thousandSeparator($companyId));
    }

    public static function usesBangladeshiGrouping(int $companyId): bool
    {
        return strtoupper((string) Setting::get($companyId, 'currency_code', 'USD')) === 'BDT';
    }

    /**
     * Bangladeshi / Indian numbering: last 3 digits, then pairs of 2.
     * Examples: 5,000 | 1,50,000 | 2,50,00,000
     */
    public static function formatWithBangladeshiGrouping(float $amount, int $decimals = 2): string
    {
        $negative = $amount < 0;
        $amount = round(abs($amount), max(0, $decimals));

        $intPart = (int) floor($amount);
        $fraction = $decimals > 0
            ? (int) round(($amount - $intPart) * (10 ** $decimals))
            : 0;

        $intStr = (string) $intPart;
        $len = strlen($intStr);

        if ($len <= 3) {
            $formatted = $intStr;
        } else {
            $lastThree = substr($intStr, -3);
            $leading = substr($intStr, 0, -3);
            $groups = [];

            while ($leading !== '') {
                if (strlen($leading) <= 2) {
                    array_unshift($groups, $leading);
                    break;
                }

                array_unshift($groups, substr($leading, -2));
                $leading = substr($leading, 0, -2);
            }

            $formatted = implode(',', $groups) . ',' . $lastThree;
        }

        if ($decimals > 0) {
            $formatted .= '.' . str_pad((string) $fraction, $decimals, '0', STR_PAD_LEFT);
        }

        return ($negative ? '-' : '') . $formatted;
    }

    public static function thousandSeparator(int $companyId): string
    {
        $separator = (string) Setting::get($companyId, 'thousand_separator', ',');

        return match ($separator) {
            'comma', ',' => ',',
            'period', '.' => '.',
            'space', ' ' => ' ',
            default => strlen($separator) === 1 ? $separator : ',',
        };
    }

    /**
     * Parse a user-entered amount that may use US, UK, EU, Bangladeshi, Indian,
     * or space-separated thousand grouping. Respects company currency settings
     * when the format is ambiguous.
     */
    public static function parseAmount(mixed $value, ?int $companyId = null): ?float
    {
        $raw = trim((string) $value);
        if ($raw === '' || $raw === '—') {
            return null;
        }

        $negative = false;
        if (preg_match('/^\((.+)\)$/', $raw, $matches)) {
            $negative = true;
            $raw = trim($matches[1]);
        } elseif (str_starts_with($raw, '-') || str_starts_with($raw, '−')) {
            $negative = true;
            $raw = ltrim($raw, "-− \t");
        }

        $raw = preg_replace('/[\$£€₹৳¥]/u', '', $raw) ?? '';
        $raw = trim($raw);

        if ($raw === '' || $raw === '-') {
            return null;
        }

        $normalized = self::normalizeAmountString($raw, $companyId);
        if ($normalized === null || !is_numeric($normalized)) {
            return null;
        }

        $amount = (float) $normalized;

        return $negative ? -$amount : $amount;
    }

    private static function normalizeAmountString(string $raw, ?int $companyId): ?string
    {
        $raw = preg_replace('/\s+/u', '', $raw) ?? '';
        if ($raw === '') {
            return null;
        }

        $lastComma = strrpos($raw, ',');
        $lastDot = strrpos($raw, '.');

        if ($lastComma !== false && $lastDot !== false) {
            if ($lastComma > $lastDot) {
                $raw = str_replace('.', '', $raw);
                $raw = str_replace(',', '.', $raw);
            } else {
                $raw = str_replace(',', '', $raw);
            }
        } elseif ($lastComma !== false) {
            $afterComma = substr($raw, $lastComma + 1);
            $commaCount = substr_count($raw, ',');

            if ($commaCount === 1 && strlen($afterComma) > 0 && strlen($afterComma) <= 2) {
                $raw = str_replace(',', '.', $raw);
            } else {
                $raw = str_replace(',', '', $raw);
            }
        } elseif ($lastDot !== false) {
            $afterDot = substr($raw, $lastDot + 1);
            $dotCount = substr_count($raw, '.');

            if ($dotCount === 1 && strlen($afterDot) > 0 && strlen($afterDot) <= 2) {
                // 1234.56
            } elseif ($companyId && self::thousandSeparator($companyId) === '.' && $dotCount > 1) {
                $raw = str_replace('.', '', $raw);
            } elseif ($dotCount > 1) {
                $raw = str_replace('.', '', $raw);
            }
        }

        return $raw === '' ? null : $raw;
    }

    // ── Amount in words ───────────────────────────────────────────────────────
    public static function amountInWordsForCompany(float $amount, int $companyId): string
    {
        $currencyCode = strtoupper((string) Setting::get($companyId, 'currency_code', 'USD'));
        $language = (string) Setting::get($companyId, 'amount_in_words_language', 'en');

        if ($language === 'bn') {
            return self::amountInWordsBengaliForCurrency($amount, $currencyCode);
        }

        return self::amountInWordsEnglishForCurrency($amount, $currencyCode);
    }

    public static function amountInWords(float $amount, string $currencyName = 'Dollar', string $centName = 'Cent'): string
    {
        $amount = round($amount, 2);
        $whole = (int) floor($amount);
        $fraction = (int) round(($amount - $whole) * 100);

        $words = self::numberToWordsWestern($whole) . ' ' . $currencyName . ($whole !== 1 ? 's' : '');

        if ($fraction > 0) {
            $words .= ' and ' . self::numberToWordsWestern($fraction) . ' ' . $centName . ($fraction !== 1 ? 's' : '');
        }

        return ucfirst($words) . ' Only';
    }

    public static function amountInWordsBengali(float $amount): string
    {
        return self::amountInWordsBengaliForCurrency($amount, 'BDT');
    }

    public static function amountInWordsEnglishForCurrency(float $amount, string $currencyCode): string
    {
        [$mainWord, $centWord] = self::currencyWordLabels($currencyCode);
        $amount = round($amount, 2);
        $intPart = (int) floor($amount);
        $decPart = (int) round(($amount - $intPart) * 100);

        if (self::usesIndianAmountInWords($currencyCode)) {
            $words = self::englishIndianNumberToWords($intPart) ?: 'Zero';
            $result = $words . ' ' . $mainWord . ' and ';
            $result .= $decPart > 0
                ? self::englishIndianNumberToWords($decPart) . ' ' . $centWord
                : $centWord . ' Zero';

            return $result . ' only';
        }

        $words = self::numberToWordsWestern($intPart);
        $result = ucfirst($words ?: 'zero') . ' ' . $mainWord . ($intPart !== 1 ? 's' : '');

        if ($decPart > 0) {
            $result .= ' and ' . self::numberToWordsWestern($decPart) . ' ' . $centWord . ($decPart !== 1 ? 's' : '');
        }

        return $result . ' Only';
    }

    public static function amountInWordsBengaliForCurrency(float $amount, string $currencyCode): string
    {
        [$mainWord, $centWord] = self::currencyWordLabelsBengali($currencyCode);
        $amount = round($amount, 2);
        $intPart = (int) floor($amount);
        $decPart = (int) round(($amount - $intPart) * 100);

        if (self::usesIndianAmountInWords($currencyCode)) {
            $words = self::bengaliIndianNumberToWords($intPart) ?: 'শূন্য';
            $result = $words . ' ' . $mainWord;

            if ($decPart > 0) {
                $result .= ' এবং ' . self::bengaliIndianNumberToWords($decPart) . ' ' . $centWord;
            } else {
                $result .= ' এবং ' . $centWord . ' শূন্য';
            }

            return $result . ' মাত্র';
        }

        $words = self::bengaliIndianNumberToWords($intPart) ?: 'শূন্য';
        $result = $words . ' ' . $mainWord;

        if ($decPart > 0) {
            $result .= ' এবং ' . self::bengaliIndianNumberToWords($decPart) . ' ' . $centWord;
        }

        return $result . ' মাত্র';
    }

    public static function usesIndianAmountInWords(string $currencyCode): bool
    {
        return in_array(strtoupper($currencyCode), ['BDT', 'INR'], true);
    }

    /** @return array{0: string, 1: string} */
    public static function currencyWordLabels(string $currencyCode): array
    {
        return match (strtoupper($currencyCode)) {
            'BDT' => ['Taka', 'Paisa'],
            'GBP' => ['Pound', 'Pence'],
            'EUR' => ['Euro', 'Cent'],
            'INR' => ['Rupee', 'Paisa'],
            'AED' => ['Dirham', 'Fils'],
            'SAR' => ['Riyal', 'Halala'],
            default => ['Dollar', 'Cent'],
        };
    }

    /** @return array{0: string, 1: string} */
    private static function currencyWordLabelsBengali(string $currencyCode): array
    {
        return match (strtoupper($currencyCode)) {
            'BDT', 'INR' => ['টাকা', 'পয়সা'],
            'GBP' => ['পাউন্ড', 'পেন্স'],
            'EUR' => ['ইউরো', 'সেন্ট'],
            'AED' => ['দিরহাম', 'ফিলস'],
            'SAR' => ['রিয়াল', 'হালালা'],
            default => ['ডলার', 'সেন্ট'],
        };
    }

    private static function englishIndianNumberToWords(int $n): string
    {
        $ones = ['', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine',
            'Ten', 'Eleven', 'Twelve', 'Thirteen', 'Fourteen', 'Fifteen', 'Sixteen',
            'Seventeen', 'Eighteen', 'Nineteen'];
        $tens = ['', '', 'Twenty', 'Thirty', 'Forty', 'Fifty', 'Sixty', 'Seventy', 'Eighty', 'Ninety'];

        $result = '';

        if ($n >= 10000000) {
            $result .= self::englishBelowThousand((int) ($n / 10000000), $ones, $tens) . ' Crore ';
            $n %= 10000000;
        }
        if ($n >= 100000) {
            $result .= self::englishBelowThousand((int) ($n / 100000), $ones, $tens) . ' Lakh ';
            $n %= 100000;
        }
        if ($n >= 1000) {
            $result .= self::englishBelowThousand((int) ($n / 1000), $ones, $tens) . ' Thousand ';
            $n %= 1000;
        }
        if ($n > 0) {
            $result .= self::englishBelowThousand($n, $ones, $tens) . ' ';
        }

        return trim($result);
    }

    private static function englishBelowThousand(int $n, array $ones, array $tens): string
    {
        if ($n <= 0) {
            return '';
        }
        if ($n < 20) {
            return $ones[$n];
        }
        if ($n < 100) {
            return $tens[(int) ($n / 10)] . ($n % 10 ? ' ' . $ones[$n % 10] : '');
        }

        return $ones[(int) ($n / 100)] . ' Hundred' . ($n % 100 ? ' ' . self::englishBelowThousand($n % 100, $ones, $tens) : '');
    }

    private static function bengaliIndianNumberToWords(int $n): string
    {
        $ones = ['', 'এক', 'দুই', 'তিন', 'চার', 'পাঁচ', 'ছয়', 'সাত', 'আট', 'নয়',
            'দশ', 'এগারো', 'বারো', 'তেরো', 'চোদ্দ', 'পনেরো', 'ষোল', 'সতেরো', 'আঠারো', 'উনিশ'];
        $tens = ['', '', 'বিশ', 'ত্রিশ', 'চল্লিশ', 'পঞ্চাশ', 'ষাট', 'সত্তর', 'আশি', 'নব্বই'];

        $result = '';

        if ($n >= 10000000) {
            $result .= self::bengaliBelowThousand((int) ($n / 10000000), $ones, $tens) . ' কোটি ';
            $n %= 10000000;
        }
        if ($n >= 100000) {
            $result .= self::bengaliBelowThousand((int) ($n / 100000), $ones, $tens) . ' লাখ ';
            $n %= 100000;
        }
        if ($n >= 1000) {
            $result .= self::bengaliBelowThousand((int) ($n / 1000), $ones, $tens) . ' হাজার ';
            $n %= 1000;
        }
        if ($n > 0) {
            $result .= self::bengaliBelowThousand($n, $ones, $tens) . ' ';
        }

        return trim($result);
    }

    private static function bengaliBelowThousand(int $n, array $ones, array $tens): string
    {
        if ($n <= 0) {
            return '';
        }
        if ($n < 20) {
            return $ones[$n];
        }
        if ($n < 100) {
            $word = $tens[(int) ($n / 10)];

            return $n % 10 ? $word . ' ' . $ones[$n % 10] : $word;
        }

        $word = $ones[(int) ($n / 100)] . 'শ';

        return $n % 100 ? $word . ' ' . self::bengaliBelowThousand($n % 100, $ones, $tens) : $word;
    }

    // ── Internal helpers ──────────────────────────────────────────────────────
    private static function numberToWordsWestern(int $n): string
    {
        if ($n === 0) {
            return 'zero';
        }

        $ones  = ['','one','two','three','four','five','six','seven','eight','nine',
                  'ten','eleven','twelve','thirteen','fourteen','fifteen','sixteen',
                  'seventeen','eighteen','nineteen'];
        $tens  = ['','','twenty','thirty','forty','fifty','sixty','seventy','eighty','ninety'];
        $scale = ['','thousand','million','billion','trillion'];

        $parts = [];
        $i     = 0;

        while ($n > 0) {
            $chunk = $n % 1000;
            if ($chunk !== 0) {
                $str = '';
                if ($chunk >= 100) {
                    $str .= $ones[$chunk / 100] . ' hundred ';
                    $chunk %= 100;
                }
                if ($chunk >= 20) {
                    $str .= $tens[$chunk / 10] . ' ';
                    $chunk %= 10;
                }
                if ($chunk > 0) {
                    $str .= $ones[$chunk] . ' ';
                }
                $parts[] = trim($str) . ($scale[$i] ? ' ' . $scale[$i] : '');
            }
            $n = (int) ($n / 1000);
            $i++;
        }

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