<?php

declare(strict_types=1);

namespace App\Services\Import;

use App\Core\SchemaInspector;
use PDO;
use RuntimeException;
use Throwable;

final class BulkImportService
{
    public function __construct(private PDO $pdo) {}

    public function ensureSchema(): void
    {
        $this->pdo->exec("CREATE TABLE IF NOT EXISTS import_jobs (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
            import_type VARCHAR(40) NOT NULL,
            file_name VARCHAR(255) NULL,
            status VARCHAR(30) NOT NULL DEFAULT 'preview',
            total_rows INT NOT NULL DEFAULT 0,
            valid_rows INT NOT NULL DEFAULT 0,
            duplicate_rows INT NOT NULL DEFAULT 0,
            error_rows INT NOT NULL DEFAULT 0,
            imported_rows INT NOT NULL DEFAULT 0,
            skipped_rows INT NOT NULL DEFAULT 0,
            created_by BIGINT UNSIGNED NULL,
            created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
            executed_at DATETIME NULL,
            KEY idx_import_jobs_type_date (import_type, created_at)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
        $this->pdo->exec("CREATE TABLE IF NOT EXISTS import_job_rows (
            id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
            job_id BIGINT UNSIGNED NOT NULL,
            `row_number` INT NOT NULL,
            status VARCHAR(30) NOT NULL,
            message VARCHAR(255) NULL,
            raw_data_json LONGTEXT NULL,
            normalized_data_json LONGTEXT NULL,
            created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
            KEY idx_import_rows_job_status (job_id, status),
            KEY idx_import_rows_job (job_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
        // Keep the index page lightweight and safe: do not create or alter domain tables here.
        // Domain tables/columns are checked only when executing an import.
    }

    public function types(): array
    {
        return [
            'items' => 'الأصناف',
            'customers' => 'العملاء',
            'suppliers' => 'الموردين',
        ];
    }

    /** @return array<int,array<int,string>> */
    public function templateRows(string $type): array
    {
        if ($type === 'items') {
            return [
                ['name','barcode','category','unit','cost_price','sale_price','default_supplier','reorder_level','opening_warehouse','opening_qty','is_active','notes'],
                ['صنف تجريبي 001','BAR-001','أدوية','قطعة','10.50','15.00','مورد تجريبي','5','المخزن الرئيسي','100','1','ملاحظات اختيارية'],
            ];
        }
        return [
            ['code','name','phone','email','tax_number','address','opening_balance','credit_limit','is_active','notes'],
            [$type === 'customers' ? 'C-001' : 'S-001', $type === 'customers' ? 'عميل تجريبي' : 'مورد تجريبي', '01000000000', 'demo@example.com', '', 'العنوان', '0', $type === 'customers' ? '1000' : '0', '1', 'ملاحظات اختيارية'],
        ];
    }

    public function createJobFromUpload(string $type, array $file, ?int $userId): int
    {
        $this->ensureSchema();
        if (!isset($this->types()[$type])) {
            throw new RuntimeException('نوع الاستيراد غير مدعوم.');
        }
        if (empty($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) {
            throw new RuntimeException('من فضلك اختر ملف Excel.');
        }
        $name = (string)($file['name'] ?? 'upload.xlsx');
        $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
        if (!in_array($ext, ['xlsx','csv'], true)) {
            throw new RuntimeException('الملف يجب أن يكون XLSX أو CSV.');
        }
        $rows = SimpleXlsx::read((string)$file['tmp_name'], $name);
        if (count($rows) < 2) {
            throw new RuntimeException('الملف لا يحتوي على بيانات.');
        }
        $headers = array_map([$this, 'normalizeHeader'], $rows[0]);
        $dataRows = array_slice($rows, 1, 5000);

        $this->pdo->beginTransaction();
        try {
            $st = $this->pdo->prepare("INSERT INTO import_jobs (import_type, file_name, status, created_by) VALUES (?,?, 'preview', ?)");
            $st->execute([$type, $name, $userId]);
            $jobId = (int)$this->pdo->lastInsertId();

            $stats = ['total'=>0, 'valid'=>0, 'duplicate'=>0, 'error'=>0];
            $seen = [];
            foreach ($dataRows as $idx => $row) {
                if ($this->isEmptyRow($row)) { continue; }
                $stats['total']++;
                $raw = $this->combine($headers, $row);
                [$status, $message, $normalized, $uniqueKey] = $this->validateRow($type, $raw, $seen);
                if ($uniqueKey !== '') { $seen[$uniqueKey] = true; }
                $stats[$status === 'valid' ? 'valid' : ($status === 'duplicate' ? 'duplicate' : 'error')]++;
                $this->insertRow($jobId, $idx + 2, $status, $message, $raw, $normalized);
            }
            $up = $this->pdo->prepare("UPDATE import_jobs SET total_rows=?, valid_rows=?, duplicate_rows=?, error_rows=? WHERE id=?");
            $up->execute([$stats['total'], $stats['valid'], $stats['duplicate'], $stats['error'], $jobId]);
            $this->pdo->commit();
            return $jobId;
        } catch (Throwable $e) {
            $this->pdo->rollBack();
            throw $e;
        }
    }

    public function executeJob(int $jobId): array
    {
        $this->ensureSchema();
        $job = $this->job($jobId);
        if (!$job) { throw new RuntimeException('عملية الاستيراد غير موجودة.'); }
        if (($job['status'] ?? '') === 'executed') { throw new RuntimeException('تم تنفيذ هذه العملية من قبل.'); }
        $this->ensureBasicTables();
        if (($job['import_type'] ?? '') === 'items') {
            $this->ensureItemColumns();
        }
        $rows = $this->rows($jobId, 10000, 'valid');
        $imported = 0;
        $skipped = (int)($job['duplicate_rows'] ?? 0);
        $this->pdo->beginTransaction();
        try {
            foreach ($rows as $row) {
                $data = json_decode((string)$row['normalized_data_json'], true) ?: [];
                if (($job['import_type'] ?? '') === 'items') {
                    $this->insertItem($data);
                } elseif (($job['import_type'] ?? '') === 'customers') {
                    $this->insertParty($data, true, false);
                } elseif (($job['import_type'] ?? '') === 'suppliers') {
                    $this->insertParty($data, false, true);
                }
                $this->pdo->prepare("UPDATE import_job_rows SET status='imported' WHERE id=?")->execute([(int)$row['id']]);
                $imported++;
            }
            $this->pdo->prepare("UPDATE import_jobs SET status='executed', imported_rows=?, skipped_rows=?, executed_at=NOW() WHERE id=?")->execute([$imported, $skipped, $jobId]);
            $this->pdo->commit();
            return ['imported' => $imported, 'skipped' => $skipped];
        } catch (Throwable $e) {
            $this->pdo->rollBack();
            throw $e;
        }
    }

    public function recentJobs(): array
    {
        $this->ensureSchema();
        return $this->pdo->query("SELECT * FROM import_jobs ORDER BY id DESC LIMIT 20")->fetchAll() ?: [];
    }

    public function clearLogs(): int
    {
        $this->ensureSchema();
        $count = (int)($this->pdo->query("SELECT COUNT(*) FROM import_jobs")->fetchColumn() ?: 0);
        $this->pdo->beginTransaction();
        try {
            $this->pdo->exec("DELETE FROM import_job_rows");
            $this->pdo->exec("DELETE FROM import_jobs");
            $this->pdo->commit();
            return $count;
        } catch (Throwable $e) {
            if ($this->pdo->inTransaction()) {
                $this->pdo->rollBack();
            }
            throw $e;
        }
    }

    public function job(int $id): array|false
    {
        $st = $this->pdo->prepare('SELECT * FROM import_jobs WHERE id=? LIMIT 1');
        $st->execute([$id]);
        return $st->fetch();
    }

    public function rows(int $jobId, int $limit = 300, string $status = ''): array
    {
        if ($status !== '') {
            $st = $this->pdo->prepare('SELECT * FROM import_job_rows WHERE job_id=? AND status=? ORDER BY `row_number` ASC LIMIT ' . max(1, min(10000, $limit)));
            $st->execute([$jobId, $status]);
        } else {
            $st = $this->pdo->prepare('SELECT * FROM import_job_rows WHERE job_id=? ORDER BY `row_number` ASC LIMIT ' . max(1, min(10000, $limit)));
            $st->execute([$jobId]);
        }
        return $st->fetchAll() ?: [];
    }

    private function validateRow(string $type, array $raw, array $seen): array
    {
        if ($type === 'items') {
            $name = $this->cleanText($raw['name'] ?? '');
            $barcode = $this->cleanCode($raw['barcode'] ?? '');
            if ($name === '') { return ['error', 'اسم الصنف مطلوب.', [], '']; }
            if ($barcode !== '' && (isset($seen['item_barcode:' . $barcode]) || $this->existsItemBarcode($barcode))) {
                return ['duplicate', 'تم تخطي الصف لأن الباركود موجود مسبقًا.', [], 'item_barcode:' . $barcode];
            }
            $cost = $this->num($raw['cost_price'] ?? 0);
            $price = $this->num($raw['sale_price'] ?? 0);
            $qty = $this->num($raw['opening_qty'] ?? 0);
            if ($cost < 0 || $price < 0 || $qty < 0) { return ['error', 'الأسعار أو الرصيد الافتتاحي لا يمكن أن تكون سالبة.', [], '']; }
            $normalized = [
                'name'=>$name,'barcode'=>$barcode ?: null,'category'=>$this->cleanText($raw['category'] ?? ''),
                'unit'=>$this->cleanText($raw['unit'] ?? '') ?: 'قطعة','cost'=>$cost,'price'=>$price,
                'default_supplier'=>$this->cleanText($raw['default_supplier'] ?? ''),
                'reorder_level'=>$this->num($raw['reorder_level'] ?? 0),'opening_warehouse'=>$this->cleanText($raw['opening_warehouse'] ?? ''),
                'opening_qty'=>$qty,'is_active'=>$this->bool($raw['is_active'] ?? '', 1),'notes'=>$this->cleanText($raw['notes'] ?? '') ?: null,
            ];
            $nameKey = function_exists('mb_strtolower') ? mb_strtolower($name) : strtolower($name);
            return ['valid', 'جاهز للاستيراد.', $normalized, $barcode !== '' ? 'item_barcode:' . $barcode : 'item_name:' . $nameKey];
        }

        $name = $this->cleanText($raw['name'] ?? '');
        $code = $this->cleanCode($raw['code'] ?? '');
        $phone = $this->cleanCode($raw['phone'] ?? '');
        $tax = $this->cleanCode($raw['tax_number'] ?? $raw['tax_no'] ?? '');
        if ($name === '') { return ['error', 'الاسم مطلوب.', [], '']; }
        if ($code === '' && $phone === '' && $tax === '') { return ['error', 'يجب إدخال الكود أو الهاتف أو الرقم الضريبي لكشف التكرار.', [], '']; }
        $dupKey = $code !== '' ? 'party_code:' . $code : ($phone !== '' ? 'party_phone:' . $phone : 'party_tax:' . $tax);
        if (isset($seen[$dupKey]) || $this->existsParty($code, $phone, $tax)) {
            return ['duplicate', 'تم تخطي الصف لأن الطرف موجود مسبقًا.', [], $dupKey];
        }
        $normalized = [
            'code'=>$code ?: null,'name'=>$name,'phone'=>$phone ?: null,'email'=>$this->cleanCode($raw['email'] ?? '') ?: null,
            'tax_no'=>$tax ?: null,'address'=>$this->cleanText($raw['address'] ?? '') ?: null,
            'opening_balance'=>$this->num($raw['opening_balance'] ?? 0),'credit_limit'=>$this->num($raw['credit_limit'] ?? 0),
            'is_active'=>$this->bool($raw['is_active'] ?? '', 1),'notes'=>$this->cleanText($raw['notes'] ?? '') ?: null,
        ];
        return ['valid', 'جاهز للاستيراد.', $normalized, $dupKey];
    }

    private function insertItem(array $d): void
    {
        $categoryId = $d['category'] !== '' ? $this->ensureCategory($d['category']) : null;
        $unitId = $this->ensureUnit($d['unit'] ?: 'قطعة');
        $supplierId = $d['default_supplier'] !== '' ? $this->findSupplierId($d['default_supplier']) : null;
        $cols = ['name','barcode','category_id','default_unit_id','cost','price','reorder_point','low_stock_level','notes','is_active'];
        $vals = [$d['name'],$d['barcode'],$categoryId,$unitId,$d['cost'],$d['price'],$d['reorder_level'],$d['reorder_level'],$d['notes'],$d['is_active']];
        if (SchemaInspector::columnExists($this->pdo, 'items', 'default_supplier_id')) {
            $cols[] = 'default_supplier_id'; $vals[] = $supplierId;
        }
        if (SchemaInspector::columnExists($this->pdo, 'items', 'brand')) {
            $cols[] = 'brand'; $vals[] = null;
        }
        $placeholders = implode(',', array_fill(0, count($cols), '?'));
        $this->pdo->prepare('INSERT INTO items (`' . implode('`,`', $cols) . '`) VALUES (' . $placeholders . ')')->execute($vals);
        $itemId = (int)$this->pdo->lastInsertId();
        if ($itemId > 0 && SchemaInspector::tableExists($this->pdo, 'item_units')) {
            try { $this->pdo->prepare("INSERT IGNORE INTO item_units (item_id, unit_id, factor_to_base, is_base) VALUES (?,?,1,1)")->execute([$itemId, $unitId]); } catch (Throwable) {}
        }
        if ($itemId > 0 && $d['opening_qty'] > 0 && $d['opening_warehouse'] !== '') {
            $warehouseId = $this->ensureWarehouse($d['opening_warehouse']);
            $this->pdo->prepare("INSERT INTO stock_balances (warehouse_id,item_id,qty) VALUES (?,?,?) ON DUPLICATE KEY UPDATE qty=qty+VALUES(qty)")->execute([$warehouseId, $itemId, $d['opening_qty']]);
            if (SchemaInspector::tableExists($this->pdo, 'stock_moves')) {
                $userId = (int)($_SESSION['user']['id'] ?? 1);
                $this->pdo->prepare("INSERT INTO stock_moves (ref_type, ref_id, warehouse_id, item_id, qty_in, qty_out, cost, user_id) VALUES ('IMPORT_OPENING',0,?,?,?,?,?,?)")->execute([$warehouseId, $itemId, $d['opening_qty'], 0, $d['cost'], $userId]);
            }
        }
    }

    private function insertParty(array $d, bool $customer, bool $supplier): void
    {
        $this->pdo->prepare("INSERT INTO parties (code,name,phone,phone2,email,address,tax_no,notes,opening_balance,credit_limit,is_customer,is_supplier,is_active,created_by) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)")
            ->execute([$d['code'],$d['name'],$d['phone'],null,$d['email'],$d['address'],$d['tax_no'],$d['notes'],$d['opening_balance'],$d['credit_limit'],$customer ? 1 : 0,$supplier ? 1 : 0,$d['is_active'],$_SESSION['user']['id'] ?? null]);
    }

    private function ensureBasicTables(): void
    {
        $this->pdo->exec("CREATE TABLE IF NOT EXISTS categories (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(160) NOT NULL UNIQUE, is_active TINYINT(1) NOT NULL DEFAULT 1) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        $this->pdo->exec("CREATE TABLE IF NOT EXISTS units (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(80) NOT NULL UNIQUE, symbol VARCHAR(20) NULL, is_active TINYINT(1) NOT NULL DEFAULT 1) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        $this->pdo->exec("CREATE TABLE IF NOT EXISTS warehouses (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(120) NOT NULL UNIQUE, is_active TINYINT(1) NOT NULL DEFAULT 1) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        $this->pdo->exec("CREATE TABLE IF NOT EXISTS parties (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, code VARCHAR(40) NULL UNIQUE, name VARCHAR(160) NOT NULL, phone VARCHAR(40) NULL, phone2 VARCHAR(40) NULL, email VARCHAR(160) NULL, address VARCHAR(255) NULL, tax_no VARCHAR(80) NULL, notes VARCHAR(255) NULL, opening_balance DECIMAL(14,2) NOT NULL DEFAULT 0, credit_limit DECIMAL(14,2) NOT NULL DEFAULT 0, is_customer TINYINT(1) NOT NULL DEFAULT 0, is_supplier TINYINT(1) NOT NULL DEFAULT 0, is_active TINYINT(1) NOT NULL DEFAULT 1, created_by BIGINT UNSIGNED NULL, created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
    }

    private function ensureItemColumns(): void
    {
        if (!SchemaInspector::tableExists($this->pdo, 'items')) {
            $this->pdo->exec("CREATE TABLE IF NOT EXISTS items (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(200) NOT NULL, barcode VARCHAR(64) NULL UNIQUE, cost DECIMAL(12,2) NOT NULL DEFAULT 0, price DECIMAL(12,2) NOT NULL DEFAULT 0, is_active TINYINT(1) NOT NULL DEFAULT 1, created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
        }
        try { SchemaInspector::ensureColumn($this->pdo, 'items', 'category_id', 'BIGINT UNSIGNED NULL'); } catch (Throwable) {}
        try { SchemaInspector::ensureColumn($this->pdo, 'items', 'default_unit_id', 'BIGINT UNSIGNED NULL'); } catch (Throwable) {}
        try { SchemaInspector::ensureColumn($this->pdo, 'items', 'reorder_point', 'DECIMAL(14,3) NOT NULL DEFAULT 0'); } catch (Throwable) {}
        try { SchemaInspector::ensureColumn($this->pdo, 'items', 'low_stock_level', 'DECIMAL(14,3) NOT NULL DEFAULT 0'); } catch (Throwable) {}
        try { SchemaInspector::ensureColumn($this->pdo, 'items', 'notes', 'VARCHAR(255) NULL'); } catch (Throwable) {}
        try { SchemaInspector::ensureColumn($this->pdo, 'items', 'default_supplier_id', 'BIGINT UNSIGNED NULL'); } catch (Throwable) {}
    }

    private function ensureCategory(string $name): int { return $this->ensureByName('categories', $name); }
    private function ensureUnit(string $name): int { return $this->ensureByName('units', $name); }
    private function ensureWarehouse(string $name): int { return $this->ensureByName('warehouses', $name); }

    private function ensureByName(string $table, string $name): int
    {
        $name = trim($name);
        if ($name === '') { return 0; }
        $st = $this->pdo->prepare("SELECT id FROM {$table} WHERE name=? LIMIT 1");
        $st->execute([$name]);
        $id = (int)($st->fetchColumn() ?: 0);
        if ($id > 0) { return $id; }

        if ($table === 'units') {
            $cols = ['name']; $vals = [$name];
            if (SchemaInspector::columnExists($this->pdo, 'units', 'symbol')) { $cols[] = 'symbol'; $vals[] = function_exists('mb_substr') ? mb_substr($name, 0, 8) : substr($name, 0, 8); }
            $sql = "INSERT INTO units (`" . implode('`,`', $cols) . "`) VALUES (" . implode(',', array_fill(0, count($cols), '?')) . ")";
            $this->pdo->prepare($sql)->execute($vals);
        } elseif ($table === 'warehouses') {
            $cols = ['name']; $vals = [$name];
            if (SchemaInspector::columnExists($this->pdo, 'warehouses', 'code')) { $cols[] = 'code'; $vals[] = 'WH-' . substr(md5($name), 0, 8); }
            $sql = "INSERT INTO warehouses (`" . implode('`,`', $cols) . "`) VALUES (" . implode(',', array_fill(0, count($cols), '?')) . ")";
            $this->pdo->prepare($sql)->execute($vals);
        } else {
            $this->pdo->prepare("INSERT INTO {$table} (name) VALUES (?)")->execute([$name]);
        }
        return (int)$this->pdo->lastInsertId();
    }

    private function findSupplierId(string $name): ?int
    {
        $st = $this->pdo->prepare("SELECT id FROM parties WHERE (name=? OR code=? OR phone=?) AND COALESCE(is_supplier,0)=1 LIMIT 1");
        $st->execute([$name,$name,$name]);
        $id = (int)($st->fetchColumn() ?: 0);
        return $id > 0 ? $id : null;
    }

    private function existsItemBarcode(string $barcode): bool
    {
        $st = $this->pdo->prepare('SELECT 1 FROM items WHERE barcode=? LIMIT 1'); $st->execute([$barcode]); return (bool)$st->fetchColumn();
    }

    private function existsParty(string $code, string $phone, string $tax): bool
    {
        $where = []; $params = [];
        if ($code !== '') { $where[] = 'code=?'; $params[] = $code; }
        if ($phone !== '') { $where[] = 'phone=?'; $params[] = $phone; }
        if ($tax !== '') { $where[] = 'tax_no=?'; $params[] = $tax; }
        if (!$where) { return false; }
        $st = $this->pdo->prepare('SELECT 1 FROM parties WHERE ' . implode(' OR ', $where) . ' LIMIT 1');
        $st->execute($params);
        return (bool)$st->fetchColumn();
    }

    private function insertRow(int $jobId, int $rowNo, string $status, string $message, array $raw, array $norm): void
    {
        $this->pdo->prepare("INSERT INTO import_job_rows (job_id,`row_number`,status,message,raw_data_json,normalized_data_json) VALUES (?,?,?,?,?,?)")
            ->execute([$jobId,$rowNo,$status,$message,json_encode($raw, JSON_UNESCAPED_UNICODE),json_encode($norm, JSON_UNESCAPED_UNICODE)]);
    }

    private function combine(array $headers, array $row): array
    {
        $out = [];
        foreach ($headers as $i => $h) {
            if ($h !== '') {
                $out[$h] = $this->cleanText($row[$i] ?? '');
            }
        }
        return $out;
    }

    private function isEmptyRow(array $row): bool
    {
        return $this->cleanText(implode('', array_map('strval', $row))) === '';
    }

    private function normalizeHeader(string $h): string
    {
        $header = $this->cleanCode($h);
        $header = preg_replace('/[^A-Za-z0-9_]+/u', '_', $header) ?? $header;
        return strtolower(trim($header, '_'));
    }

    private function num(mixed $v): float
    {
        $value = $this->cleanCode($v);
        if ($value === '') { return 0.0; }
        return (float)str_replace(',', '', $value);
    }

    private function bool(mixed $v, int $default = 1): int
    {
        $s = strtolower($this->cleanCode($v));
        if ($s === '') { return $default; }
        if (in_array($s, ['1','yes','y','true','active','enabled','on','نشط','نعم','مفعل','فعال'], true)) { return 1; }
        if (in_array($s, ['0','no','n','false','inactive','disabled','off','غير نشط','لا','معطل'], true)) { return 0; }
        return $default;
    }

    private function cleanText(mixed $value): string
    {
        $text = (string)$value;
        $text = preg_replace('/^\xEF\xBB\xBF/u', '', $text) ?? $text;
        $text = str_replace(["\xC2\xA0", "\xE2\x80\x8B", "\xE2\x80\x8C", "\xE2\x80\x8D"], ' ', $text);
        $text = preg_replace('/[[:cntrl:]]+/u', ' ', $text) ?? $text;
        $text = preg_replace('/\s+/u', ' ', $text) ?? $text;
        return trim($text);
    }

    private function cleanCode(mixed $value): string
    {
        return $this->cleanText($value);
    }
}
