<?php
namespace App;

class Utils {
    // Normalize Persian/Arabic digits to English
    public static function toEnglishDigits(string $s): string {
        $map = [
            '۰'=>'0','۱'=>'1','۲'=>'2','۳'=>'3','۴'=>'4','۵'=>'5','۶'=>'6','۷'=>'7','۸'=>'8','۹'=>'9',
            '٠'=>'0','١'=>'1','٢'=>'2','٣'=>'3','٤'=>'4','٥'=>'5','٦'=>'6','٧'=>'7','٨'=>'8','٩'=>'9',
        ];
        return strtr($s, $map);
    }

    public static function onlyDigits(string $s): string {
        return preg_replace('/\D+/', '', self::toEnglishDigits($s));
    }

    public static function parseAmountToman(string $text): ?int {
        $t = self::toEnglishDigits($text);
        // ٬ (U+066C) جداکنندهٔ هزارگان فارسی و ٫ (U+066B) اعشار فارسی هم باید در
        // کلاس باشند، وگرنه «مبلغ: ۱۲۳٬۴۵۶» فقط تا اولین جداکننده خوانده می‌شود (=۱۲۳).
        if (preg_match('/(?:مبلغ(?:\s*واریزی)?)[\s:：]*([\d,\.٬٫]+)/u', $t, $m)) {
            $num = preg_replace('/\D+/', '', $m[1]);
            if ($num !== '') return (int)$num;
        }
        // بدون fallback حدسی: اگر خط «مبلغ:» نبود، مبلغ نامشخص است و
        // رکورد با دلیل «مبلغ نامشخص» در گزارش کدهای حذف‌شده می‌آید.
        return null;
    }

    // Parse bank amount cell robustly
    public static function parseIrrCell($v): int {
        if (is_numeric($v)) return (int)round($v);
        $s = self::toEnglishDigits((string)$v);
        $s = trim($s);
        $neg = false;
        if (preg_match('/^\((.*)\)$/', $s, $m)) { $s = $m[1]; $neg = true; }
        $s = str_replace([',', '٬', ' '], '', $s);
        $s = preg_split('/[\.٫]/', $s)[0];
        $num = preg_replace('/\D+/', '', $s);
        if ($num === '') return 0;
        $val = (int)$num;
        return $neg ? -$val : $val;
    }

    // Extract KYC status value from lines like: «🔴 وضعیت احراز: unsent»
    public static function extractKycStatus(string $text): ?string {
        if (preg_match('/وضعیت\s*احراز(?:\s*هویت)?\s*[:：]\s*([^\r\n]+)/iu', $text, $m)) {
            $v = trim($m[1]);
            return $v !== '' ? $v : null;
        }
        return null;
    }

    // True if KYC status is one of the blocked states (record must be excluded from outputs)
    public static function isKycBlocked(string $text): bool {
        $status = self::extractKycStatus($text);
        if ($status === null) return false; // بدون خط احراز = عادی
        $blocked = defined('KYC_BLOCKED_STATUSES')
            ? KYC_BLOCKED_STATUSES
            : ['rejected', 'unsubmitted', 'unsent', 'waiting'];
        foreach ($blocked as $kw) {
            if (mb_stripos($status, $kw, 0, 'UTF-8') !== false) return true;
        }
        return false;
    }

    // Extract only first line after "نام:" and strip trailing "شماره کارت/شبا ..." on same line
    public static function extractName(string $text): ?string {
        $t = trim($text);
        // NOTE: از [ \t]* بعد از : استفاده می‌کنیم نه \s* چون \s* نیولاین رو هم می‌خوره
        // و باعث می‌شه خط بعدی (مثل «نام بانک:») اشتباه به عنوان نام کپچر بشه
        if (preg_match('/^\s*نام\s*[:：][ \t]*([^\r\n]+)/mu', $t, $m) ||
            preg_match('/\n\s*نام\s*[:：][ \t]*([^\r\n]+)/mu', $t, $m)) {
            $name = trim($m[1]);
            $name = preg_replace('/\s*(شماره\s*(?:کارت|شبا).*)$/u', '', $name);
            $name = trim($name);
            // اگر نام شامل : بود یا خط تیره/جای‌گذار بود یعنی نام واقعی نیست
            if ($name === '' || strpos($name, ':') !== false || strpos($name, '：') !== false) {
                return null;
            }
            if (in_array($name, ['—', '-', '–', 'نامشخص', 'استعلام ناموفق', 'null', 'none'], true)) {
                return null;
            }
            return $name;
        }
        return null;
    }

    public static function extractIBAN(string $text): ?string {
        $t = self::toEnglishDigits($text);
        if (preg_match('/\bIR[0-9A-Z]{24}\b/i', $t, $m)) {
            return strtoupper($m[0]);
        }
        return null;
    }

    public static function extractCard(string $text): ?string {
        $t = self::toEnglishDigits($text);
        if (preg_match('/\b(\d{16})\b/', $t, $m)) {
            return $m[1];
        }
        return null;
    }

    public static function extractProductCode(string $text): ?string {
        $t = trim($text);
        if (preg_match('/#\d{3,}/u', $t, $m)) return $m[0];
        return null;
    }

    // Turn "#754677" or " ##754677 " into "754677" (single canonical form)
    public static function normalizeProductCode(string $code): string {
        $code = trim((string)$code);
        // remove any leading hashes and whitespace
        $code = ltrim($code, "# \t\r\n");
        return trim($code);
    }

    // Normalize Persian names for matching (collapse spaces, fix Arabic variants, remove ZWNJ)
    public static function normalizeName(string $name): string {
        $s = trim((string)$name);
        if ($s === '') return '';
        // Arabic -> Persian letter variants
        $s = str_replace(['ي','ك','ۀ','ة','ؤ','إ','أ','ٱ','ئ'], ['ی','ک','ه','ه','و','ا','ا','ا','ی'], $s);
        // remove ZWNJ and extra punctuation
        $s = str_replace(["\u{200C}", "\u{200D}", "ـ"], ' ', $s);
        $s = preg_replace('/[\s\-\_\,\،\؛\:\;\(\)\[\]\{\}]+/u', ' ', $s);
        $s = preg_replace('/\s+/u', ' ', $s);
        return trim($s);
    }

    public static function isIban(string $s): bool {
        $t = strtoupper(trim(self::toEnglishDigits($s)));
        $t = preg_replace('/\s+/', '', $t);
        return (bool)preg_match('/^IR[0-9A-Z]{24}$/', $t);
    }

    // نرمال‌سازی حروف عربی به فارسی (برای مقایسهٔ متن‌هایی که بانک برمی‌گرداند)
    public static function normalizeArabicLetters(string $s): string {
        return str_replace(['ي', 'ى', 'ك'], ['ی', 'ی', 'ک'], $s);
    }

    /**
     * عبارت‌های صریحِ شکست.
     *
     * ⚠️ عمداً هیچ کلمهٔ کوتاه دوحرفی («رد»، «عدم») در این لیست نیست: مقایسه
     * زیررشته‌ای است و «رد» داخل «پـردازش» هم پیدا می‌شود، یعنی «آماده پردازش»
     * (وضعیت لحظهٔ ثبتِ سامان و شهر) ناموفق حساب می‌شد و کل دسته حذف می‌شد.
     */
    private const FAIL_PHRASES = [
        'ناموفق', 'برگشت', 'مردود', 'رد شده', 'ردشده', 'لغو', 'ابطال', 'باطل',
        'انصراف', 'خطا', 'عدم انجام', 'عدم پرداخت', 'نامشخص',
        'cancel', 'reject', 'fail', 'error', 'unknown',
    ];

    /**
     * آیا وضعیتِ ستون «وضعیت تراکنش» موفق است؟
     *
     * مقادیر واقعی مشاهده‌شده در فایل‌های بانک‌ها:
     *   ملی    → «موفق» / «نامشخص»            (نامشخص = ناموفق؛ ستون شرح هم «تراکنش ناموفق» می‌گیرد)
     *   سامان  → «آماده پردازش»                (همهٔ ردیف‌ها در لحظهٔ ثبت)
     *   شهر    → «آماده پردازش»                (همهٔ ردیف‌ها در لحظهٔ ثبت)
     *   مهر    → «در حال انجام انتقال وجه»     (همهٔ ردیف‌ها در لحظهٔ ثبت)
     *
     * بانک ملی وضعیت قطعی می‌دهد، پس فقط «موفق» موفق است. بقیهٔ بانک‌ها در لحظهٔ
     * ثبت وضعیت «در جریان» می‌دهند و همه موفق‌اند؛ برگشتِ چرخهٔ پایا بعداً با
     * پیامک اطلاع داده می‌شود و در این فایل نیست. پس فقط عبارت صریح شکست
     * («برگشت خورده»، «رد شده»، «ناموفق») ناموفق حساب می‌شود.
     */
    public static function isSuccessStatus(string $status, string $bank = ''): bool {
        $s = self::normalizeArabicLetters(trim($status));
        if ($s === '') return false;

        foreach (self::FAIL_PHRASES as $kw) {
            if (mb_stripos($s, $kw, 0, 'UTF-8') !== false) return false;
        }

        if ($bank === 'melli') {
            return mb_stripos($s, 'موفق', 0, 'UTF-8') !== false;
        }

        return true;
    }

    public static function sanitizeCsvField(string $s): string {
        $s = str_replace([',','，'], ' ', $s);
        $s = preg_replace('/\s+/u', ' ', $s);
        return trim($s);
    }

    public static function jsonDecode(string $path): array {
        $raw = file_get_contents($path);
        $data = json_decode($raw, true);
        if (!is_array($data)) throw new \RuntimeException('Invalid JSON file');
        return $data;
    }

    public static function ensureDir(string $dir): void {
        if (!is_dir($dir)) mkdir($dir, 0775, true);
    }


// Extract bank name from a text block like "نام بانک: سامان"
public static function extractBankName(string $text): ?string {
    $t = self::toEnglishDigits($text);
    $name = null;
    // Look for Persian label
    if (preg_match('/نام\s*بانک\s*[:：]\s*([^\r\n]+)/u', $t, $m)) {
        $name = trim($m[1]);
        // strip trailing labels if present
        $name = preg_replace('/\s*(?:نام|شماره|شبا|کارت)[:：].*$/u', '', $name);
        $name = trim($name);
    }
    // Also accept "بانک سامان" plain
    elseif (preg_match('/بانک\s+([\p{L}\s]+)/u', $t, $m)) {
        $name = trim($m[1]);
        $name = preg_replace('/\s*(?:نام|شماره|شبا|کارت)[:：].*$/u', '', $name);
        $name = trim($name);
    }
    if ($name !== null && (in_array($name, ['—', '-', '–', 'نامشخص', 'استعلام ناموفق', 'null', 'none'], true) || strpos($name, 'استعلام ناموفق') !== false)) {
        return null;
    }
    return $name !== '' ? $name : null;
}
}
