<?php

declare(strict_types=1);

require_once __DIR__ . '/../Services/LegacyBotService.php';
require_once __DIR__ . '/../Rayan/RayanPanelService.php';

class TaskRunner
{
    public function __construct(private LegacyBotService $service)
    {
    }

    public function stream(string $task, string $returnPage): void
    {
        $this->startStream();

        try {
            $result = match ($task) {
                'run_exports' => $this->runAllExports($returnPage),
                'run_unmapped' => $this->runUnmapped($returnPage),
                'generate_template' => $this->generateSmartMapTemplate($returnPage),
                'upload_map' => $this->uploadSmartMap($returnPage),
                'generate_review' => $this->generateSmartReview($returnPage),
                'upload_review' => $this->uploadSmartReview($returnPage),
                'apply_saved_review' => $this->applySavedReview($returnPage),
                'build_text' => $this->buildTextList($returnPage),
                'rayan_manual_create' => $this->rayanManualCreate($returnPage),
                'rayan_process' => $this->rayanProcess($returnPage),
                'rayan_retry' => $this->rayanRetry($returnPage),
                'rayan_submit_otp' => $this->rayanSubmitOtp($returnPage),
                'rayan_inquiry' => $this->rayanInquiry($returnPage),
                default => throw new RuntimeException('عملیات درخواستی نامعتبر است.'),
            };

            set_flash('success', $result['message']);
            $this->send([
                'type' => 'complete',
                'title' => 'عملیات کامل شد',
                'progress' => 100,
                'message' => $result['message'],
                'log' => 'عملیات با موفقیت به پایان رسید.',
                'redirect' => url($returnPage),
            ]);
        } catch (Throwable $e) {
            log_app('TASK RUNNER ERROR | ' . $task . ' | ' . $e->getMessage());
            set_flash('error', $e->getMessage());
            $this->send([
                'type' => 'error',
                'title' => 'خطا در اجرا',
                'progress' => 100,
                'message' => $e->getMessage(),
                'log' => 'اجرای عملیات با خطا متوقف شد.',
            ]);
        }
        exit;
    }

    private function runAllExports(string $returnPage): array
    {
        $config = $this->service->getLegacyConfig();
        $files = [];
        $warnings = [];

        $steps = [
            'hesabanweb' => ['label' => 'حسابران‌وب', 'runner' => fn() => runHesabanwebExporter($config)],
            'ahvazphone' => ['label' => 'اهوازفون', 'runner' => fn() => runAhvazPhoneExporter($config)],
            'farnaa' => ['label' => 'فرنا', 'runner' => fn() => runFarnaaExporter($config)],
            'hamrahtel' => ['label' => 'همراه‌تل', 'runner' => fn() => runHamrahtelExporter($config)],
            'digikala' => ['label' => 'دیجی‌کالا', 'runner' => fn() => runDigikalaExporter($config)],
            'mobile140' => ['label' => 'موبایل 140', 'runner' => fn() => runMobile140Exporter($config)],
            'technolife' => ['label' => 'تکنولایف', 'runner' => fn() => runTechnolifeExporter($config)],
        ];

        $total = count($steps);
        $index = 0;
        $this->send(['type' => 'progress', 'title' => 'استخراج همه فایل‌ها', 'progress' => 5, 'message' => 'آماده‌سازی اجرا...', 'log' => 'شروع استخراج گروهی']);

        foreach ($steps as $key => $step) {
            $index++;
            $startProgress = 5 + (int)(($index - 1) / $total * 85);
            $endProgress = 5 + (int)($index / $total * 85);
            $this->send(['type' => 'progress', 'progress' => $startProgress, 'message' => 'در حال استخراج از ' . $step['label'], 'log' => 'شروع ' . $step['label']]);

            try {
                $path = $step['runner']();
                $files[] = ['key' => $key, 'label' => $step['label'], 'path' => $path, 'basename' => basename($path)];
                $this->send(['type' => 'progress', 'progress' => $endProgress, 'message' => $step['label'] . ' با موفقیت انجام شد.', 'log' => 'تکمیل ' . $step['label'] . ' | ' . basename($path)]);
            } catch (Throwable $e) {
                $warnings[] = $step['label'] . ': ' . $e->getMessage();
                logLine($config, 'WEB EXPORT ERROR | ' . $step['label'] . ' | ' . $e->getMessage());
                $this->send(['type' => 'progress', 'progress' => $endProgress, 'message' => 'خطا در ' . $step['label'] . ' ولی اجرا ادامه دارد.', 'log' => 'هشدار ' . $step['label'] . ' | ' . $e->getMessage()]);
            }
        }

        $result = [
            'files' => $files,
            'warnings' => $warnings,
            'message' => 'استخراج همه فایل‌ها انجام شد. فایل‌های موفق: ' . count($files) . (empty($warnings) ? '' : ' | هشدارها: ' . implode(' || ', $warnings)),
        ];

        $this->service->markJob('export_all', json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
        return $result;
    }

    private function runUnmapped(string $returnPage): array
    {
        $config = $this->service->getLegacyConfig();

        $this->send([
            'type' => 'progress',
            'title' => 'ساخت فایل کالاهای مپ نشده',
            'progress' => 8,
            'message' => 'در حال خواندن مپ قیمت هوشمند...',
            'log' => 'شروع ساخت فایل کالاهای مپ نشده',
        ]);

        $map = loadSmartPriceMap($config);
        if (!$map) {
            throw new RuntimeException('فایل مپ قیمت هوشمند پیدا نشد. اول «تنظیم مپ قیمت هوشمند» را کامل کن.');
        }

        $mappedSets = extractMappedSourceKeySets($map);
        $rows = [];
        $seen = [];
        $warnings = [];

        $this->send([
            'type' => 'progress',
            'progress' => 15,
            'message' => 'مپ خوانده شد؛ شروع بررسی منابع...',
            'log' => 'تعداد ردیف مپ: ' . count($map),
        ]);

        $this->send(['type' => 'progress', 'progress' => 22, 'message' => 'در حال دریافت کالاهای حسابان‌وب...', 'log' => 'شروع حسابان‌وب']);
        try {
            $sourceRows = fetchHesabanwebRows($config);
            $added = $this->appendUnmappedHesabanRows($sourceRows, $mappedSets, $rows, $seen);
            $this->send(['type' => 'progress', 'progress' => 34, 'message' => 'حسابان‌وب بررسی شد.', 'log' => 'حسابان‌وب: ' . count($sourceRows) . ' ردیف | مپ‌نشده: ' . $added]);
        } catch (Throwable $e) {
            $warnings[] = 'حسابان‌وب: ' . $e->getMessage();
            logLine($config, 'WEB UNMAPPED HESABAN ERROR | ' . $e->getMessage());
            $this->send(['type' => 'progress', 'progress' => 34, 'message' => 'خطا در حسابان‌وب؛ ادامه با منابع بعدی...', 'log' => 'هشدار حسابان‌وب | ' . $e->getMessage()]);
        }

        $this->send(['type' => 'progress', 'progress' => 40, 'message' => 'در حال دریافت کالاهای فرنا...', 'log' => 'شروع فرنا']);
        try {
            $sourceRows = safeCollectFarnaaRawRows($config);
            $added = $this->appendUnmappedGenericRows($sourceRows, 'farnaa', 'فرنا', $mappedSets, $rows, $seen);
            $this->send(['type' => 'progress', 'progress' => 50, 'message' => 'فرنا بررسی شد.', 'log' => 'فرنا: ' . count($sourceRows) . ' ردیف | مپ‌نشده: ' . $added]);
        } catch (Throwable $e) {
            $warnings[] = 'فرنا: ' . $e->getMessage();
            logLine($config, 'WEB UNMAPPED FARNA ERROR | ' . $e->getMessage());
            $this->send(['type' => 'progress', 'progress' => 50, 'message' => 'خطا در فرنا؛ ادامه با منابع بعدی...', 'log' => 'هشدار فرنا | ' . $e->getMessage()]);
        }

        $this->send(['type' => 'progress', 'progress' => 55, 'message' => 'در حال دریافت کالاهای همراه‌تل...', 'log' => 'شروع همراه‌تل']);
        try {
            $sourceRows = safeCollectHamrahtelRawRows($config);
            $added = $this->appendUnmappedGenericRows($sourceRows, 'hamrahtel', 'همراه تل', $mappedSets, $rows, $seen);
            $this->send(['type' => 'progress', 'progress' => 63, 'message' => 'همراه‌تل بررسی شد.', 'log' => 'همراه‌تل: ' . count($sourceRows) . ' ردیف | مپ‌نشده: ' . $added]);
        } catch (Throwable $e) {
            $warnings[] = 'همراه‌تل: ' . $e->getMessage();
            logLine($config, 'WEB UNMAPPED HAMRAHTEL ERROR | ' . $e->getMessage());
            $this->send(['type' => 'progress', 'progress' => 63, 'message' => 'خطا در همراه‌تل؛ ادامه با منابع بعدی...', 'log' => 'هشدار همراه‌تل | ' . $e->getMessage()]);
        }

        $this->send(['type' => 'progress', 'progress' => 68, 'message' => 'در حال دریافت کالاهای دیجی‌کالا...', 'log' => 'شروع دیجی‌کالا']);
        try {
            $sourceRows = safeCollectDigikalaRawRows($config);
            $added = $this->appendUnmappedGenericRows($sourceRows, 'digikala', 'دیجی‌کالا', $mappedSets, $rows, $seen);
            $this->send(['type' => 'progress', 'progress' => 74, 'message' => 'دیجی‌کالا بررسی شد.', 'log' => 'دیجی‌کالا: ' . count($sourceRows) . ' ردیف | مپ‌نشده: ' . $added]);
        } catch (Throwable $e) {
            $warnings[] = 'دیجی‌کالا: ' . $e->getMessage();
            logLine($config, 'WEB UNMAPPED DIGIKALA ERROR | ' . $e->getMessage());
            $this->send(['type' => 'progress', 'progress' => 74, 'message' => 'خطا در دیجی‌کالا؛ ادامه با منابع بعدی...', 'log' => 'هشدار دیجی‌کالا | ' . $e->getMessage()]);
        }

        $this->send(['type' => 'progress', 'progress' => 78, 'message' => 'در حال دریافت کالاهای موبایل 140...', 'log' => 'شروع موبایل 140']);
        try {
            $sourceRows = safeCollectMobile140RawRows($config);
            $added = $this->appendUnmappedGenericRows($sourceRows, 'mobile140', 'mobile140', $mappedSets, $rows, $seen);
            $this->send(['type' => 'progress', 'progress' => 84, 'message' => 'موبایل 140 بررسی شد.', 'log' => 'موبایل140: ' . count($sourceRows) . ' ردیف | مپ‌نشده: ' . $added]);
        } catch (Throwable $e) {
            $warnings[] = 'موبایل140: ' . $e->getMessage();
            logLine($config, 'WEB UNMAPPED MOBILE140 ERROR | ' . $e->getMessage());
            $this->send(['type' => 'progress', 'progress' => 84, 'message' => 'خطا در موبایل 140؛ ادامه با منابع بعدی...', 'log' => 'هشدار موبایل140 | ' . $e->getMessage()]);
        }

        $this->send(['type' => 'progress', 'progress' => 88, 'message' => 'در حال دریافت کالاهای تکنولایف...', 'log' => 'شروع تکنولایف']);
        try {
            $sourceRows = safeCollectTechnolifeRawRows($config);
            $added = $this->appendUnmappedGenericRows($sourceRows, 'technolife', 'تکنولایف', $mappedSets, $rows, $seen);
            $this->send(['type' => 'progress', 'progress' => 93, 'message' => 'تکنولایف بررسی شد.', 'log' => 'تکنولایف: ' . count($sourceRows) . ' ردیف | مپ‌نشده: ' . $added]);
        } catch (Throwable $e) {
            $warnings[] = 'تکنولایف: ' . $e->getMessage();
            logLine($config, 'WEB UNMAPPED TECHNOLIFE ERROR | ' . $e->getMessage());
            $this->send(['type' => 'progress', 'progress' => 93, 'message' => 'خطا در تکنولایف؛ ادامه با منابع بعدی...', 'log' => 'هشدار تکنولایف | ' . $e->getMessage()]);
        }

        $this->send(['type' => 'progress', 'progress' => 96, 'message' => 'در حال مرتب‌سازی و ساخت فایل خروجی...', 'log' => 'کل کالاهای مپ‌نشده: ' . count($rows)]);
        usort($rows, function ($a, $b) {
            $siteCmp = strcmp(
                normalizeKeyText((string)($a['نام سایت'] ?? '')),
                normalizeKeyText((string)($b['نام سایت'] ?? ''))
            );

            if ($siteCmp !== 0) {
                return $siteCmp;
            }

            return strcmp(
                normalizeKeyText((string)($a['اسم'] ?? '')),
                normalizeKeyText((string)($b['اسم'] ?? ''))
            );
        });

        $file = rtrim((string)$config['output_dir'], '/\\') . '/unmapped_products_' . date('Y-m-d_H-i-s') . '.xls';
        writeExcelLikeXls(
            $rows,
            $file,
            ['اسم', 'کد کالا', 'نام سایت', 'لینک'],
            'کالاهای مپ نشده'
        );

        $this->service->markJob('unmapped_products', $file);

        $message = 'فایل کالاهای مپ نشده ساخته شد. | تعداد ردیف: ' . count($rows) . ' | ' . basename($file);
        if ($warnings) {
            $message .= ' | هشدارها: ' . implode(' || ', $warnings);
        }

        return ['message' => $message];
    }

    private function appendUnmappedHesabanRows(array $hesabanRows, array $mappedSets, array &$rows, array &$seen): int
    {
        $before = count($rows);

        foreach ($hesabanRows as $row) {
            $code = trim((string)($row['code'] ?? ''));
            $name = trim((string)($row['name'] ?? ''));

            $candidates = array_values(array_unique(array_filter([
                normalizeKeyText($code),
                normalizeKeyText($name),
            ], fn($x) => $x !== '')));

            if (!$candidates) {
                continue;
            }

            if (sourceCandidatesMapped($candidates, $mappedSets['hesabanweb'])) {
                continue;
            }

            if ($name === '') {
                $name = $code !== '' ? $code : '-';
            }

            $itemCode = $code !== '' ? $code : '-';
            $key = 'hesabanweb|' . normalizeKeyText($name) . '|' . normalizeKeyText($itemCode);
            if (isset($seen[$key])) {
                continue;
            }
            $seen[$key] = true;

            $rows[] = [
                'اسم'      => $name,
                'کد کالا'  => $itemCode,
                'نام سایت' => 'حسابان‌وب',
                'لینک'     => '-',
            ];
        }

        return count($rows) - $before;
    }

    private function appendUnmappedGenericRows(array $sourceRows, string $sourceKey, string $siteLabel, array $mappedSets, array &$rows, array &$seen): int
    {
        $before = count($rows);
        $mappedSet = $mappedSets[$sourceKey] ?? [];

        foreach ($sourceRows as $row) {
            $title = normalizeSpaces((string)($row['title'] ?? ''));
            $slug  = normalizeSpaces((string)($row['slug'] ?? ''));
            $color = normalizeSpaces((string)($row['color'] ?? ''));
            $link  = trim((string)($row['url'] ?? ''));

            $candidateParts = [
                normalizeKeyText($title),
                normalizeKeyText($slug),
            ];

            if ($sourceKey === 'farnaa') {
                $candidateParts[] = normalizeKeyText($title . ' | ' . $color);
            }

            $candidates = array_values(array_unique(array_filter($candidateParts, fn($x) => $x !== '')));

            if (!$candidates) {
                continue;
            }

            if (sourceCandidatesMapped($candidates, $mappedSet)) {
                continue;
            }

            $displayName = $title !== '' ? $title : '-';
            $itemCode = $slug !== '' ? $slug : extractProductCodeFromUrl($link);
            if ($sourceKey === 'farnaa') {
                $itemCode = extractProductCodeFromUrl($link);
            }
            if ($itemCode === '') {
                $itemCode = '-';
            }
            if ($link === '') {
                $link = '-';
            }

            $key = $sourceKey . '|' . normalizeKeyText($displayName) . '|' . normalizeKeyText($itemCode) . '|' . $link;
            if (isset($seen[$key])) {
                continue;
            }
            $seen[$key] = true;

            $rows[] = [
                'اسم'      => $displayName,
                'کد کالا'  => $itemCode,
                'نام سایت' => $siteLabel,
                'لینک'     => $link,
            ];
        }

        return count($rows) - $before;
    }

    private function generateSmartMapTemplate(string $returnPage): array
    {
        $this->send(['type' => 'progress', 'title' => 'ساخت قالب مپ قیمت هوشمند', 'progress' => 20, 'message' => 'داده‌ها در حال آماده‌سازی هستند...', 'log' => 'شروع ساخت قالب']);
        $file = getSmartMapTemplateFileForSend($this->service->getLegacyConfig());
        $this->service->markJob('smart_map_template', $file);
        return ['message' => 'فایل مپ قیمت هوشمند ساخته شد. | ' . basename($file)];
    }

    private function uploadSmartMap(string $returnPage): array
    {
        $legacyConfig = $this->service->getLegacyConfig();
        $appConfig = $this->service->getAppConfig();

        $this->send(['type' => 'progress', 'title' => 'ذخیره مپ قیمت هوشمند', 'progress' => 10, 'message' => 'در حال دریافت فایل آپلودی...', 'log' => 'فایل از مرورگر دریافت شد']);
        $saved = $this->saveUpload('smart_map_file', 'smart_map', $appConfig['uploads_path']);

        $this->send(['type' => 'progress', 'progress' => 45, 'message' => 'در حال خواندن فایل و استخراج ردیف‌ها...', 'log' => 'فایل ذخیره شد | ' . $saved['name']]);
        $rows = readSmartMapFile($saved['path']);
        if (!$rows) {
            throw new RuntimeException('هیچ ردیف معتبری در فایل مپ پیدا نشد.');
        }

        $this->send(['type' => 'progress', 'progress' => 80, 'message' => 'در حال ذخیره مپ قیمت...', 'log' => 'تعداد ردیف معتبر: ' . count($rows)]);
        saveSmartPriceMap($legacyConfig, $rows);
        $this->service->markJob('smart_map_upload', ($saved['name'] ?? '') . ' | rows=' . count($rows));

        return ['message' => 'مپ قیمت هوشمند ذخیره شد. | تعداد ردیف: ' . count($rows)];
    }

    private function generateSmartReview(string $returnPage): array
    {
        $legacyConfig = $this->service->getLegacyConfig();
        $actorKey = Auth::user()['username'] ?? 'admin';

        $this->send(['type' => 'progress', 'title' => 'ساخت فایل بررسی قیمت هوشمند', 'progress' => 10, 'message' => 'در حال خواندن مپ قیمت هوشمند...', 'log' => 'شروع ساخت فایل بررسی']);
        $map = loadSmartPriceMap($legacyConfig);
        if (!$map) {
            throw new RuntimeException('اول باید مپ قیمت هوشمند را تنظیم کنی.');
        }

        $this->send(['type' => 'progress', 'progress' => 18, 'message' => 'در حال دریافت داده‌های حسابران‌وب...', 'log' => 'تعداد ردیف مپ: ' . count($map)]);
        $hesabanRows = fetchHesabanwebRows($legacyConfig);

        $this->send(['type' => 'progress', 'progress' => 28, 'message' => 'در حال دریافت داده‌های فرنا...', 'log' => 'حسابران‌وب: ' . count($hesabanRows) . ' ردیف']);
        $farnaaRows = safeCollectFarnaaRawRows($legacyConfig);

        $this->send(['type' => 'progress', 'progress' => 38, 'message' => 'در حال دریافت داده‌های همراه‌تل...', 'log' => 'فرنا: ' . count($farnaaRows) . ' ردیف']);
        $hamrahtelRows = safeCollectHamrahtelRawRows($legacyConfig);

        $this->send(['type' => 'progress', 'progress' => 48, 'message' => 'در حال دریافت داده‌های دیجی‌کالا...', 'log' => 'همراه‌تل: ' . count($hamrahtelRows) . ' ردیف']);
        $digikalaRows = safeCollectDigikalaRawRows($legacyConfig);

        $this->send(['type' => 'progress', 'progress' => 58, 'message' => 'در حال دریافت داده‌های موبایل 140...', 'log' => 'دیجی‌کالا: ' . count($digikalaRows) . ' ردیف']);
        $mobile140Rows = safeCollectMobile140RawRows($legacyConfig);

        $this->send(['type' => 'progress', 'progress' => 68, 'message' => 'در حال دریافت داده‌های تکنولایف...', 'log' => 'موبایل140: ' . count($mobile140Rows) . ' ردیف']);
        $technolifeRows = safeCollectTechnolifeRawRows($legacyConfig);

        $this->send(['type' => 'progress', 'progress' => 78, 'message' => 'در حال دریافت داده‌های اهوازفون...', 'log' => 'تکنولایف: ' . count($technolifeRows) . ' ردیف']);
        $ahvazRows = fetchAhvazPhoneRows($legacyConfig);

        $this->send(['type' => 'progress', 'progress' => 86, 'message' => 'در حال ساخت ردیف‌های بررسی...', 'log' => 'اهوازفون: ' . count($ahvazRows) . ' ردیف']);
        $reviewRows = $this->buildSmartPricingReviewRowsFromFetchedData(
            $legacyConfig,
            $map,
            $hesabanRows,
            $farnaaRows,
            $hamrahtelRows,
            $digikalaRows,
            $mobile140Rows,
            $technolifeRows,
            $ahvazRows
        );
        if (!$reviewRows) {
            throw new RuntimeException('هیچ ردیفی برای بررسی قیمت هوشمند ساخته نشد.');
        }

        $this->send(['type' => 'progress', 'progress' => 94, 'message' => 'در حال ذخیره فایل و snapshot...', 'log' => 'تعداد ردیف بررسی: ' . count($reviewRows)]);
        saveLatestSmartReviewRows($legacyConfig, $reviewRows);
        $file = writeSmartPriceReviewCsv($legacyConfig, $reviewRows);
        saveSmartReviewSnapshot($legacyConfig, $actorKey, $reviewRows);
        $this->service->markJob('smart_review_generate', $file . ' | rows=' . count($reviewRows));

        return ['message' => 'فایل بررسی قیمت هوشمند ساخته شد. | ردیف: ' . count($reviewRows) . ' | ' . basename($file)];
    }

    private function uploadSmartReview(string $returnPage): array
    {
        $legacyConfig = $this->service->getLegacyConfig();
        $appConfig = $this->service->getAppConfig();
        $actorKey = Auth::user()['username'] ?? 'admin';

        $this->send(['type' => 'progress', 'title' => 'آپلود و اعمال فایل بررسی', 'progress' => 10, 'message' => 'در حال دریافت فایل نهایی...', 'log' => 'شروع آپلود فایل بررسی']);
        $saved = $this->saveUpload('smart_review_file', 'smart_review', $appConfig['uploads_path']);

        $this->send(['type' => 'progress', 'progress' => 35, 'message' => 'در حال خواندن ردیف‌های فایل...', 'log' => 'فایل ذخیره شد | ' . $saved['name']]);
        $rows = readSmartPriceReviewFile($saved['path']);
        if (!$rows) {
            throw new RuntimeException('فایل بررسی معتبر نبود یا ردیفی نداشت.');
        }

        $this->send(['type' => 'progress', 'progress' => 65, 'message' => 'در حال اعمال ردیف‌های تاییدشده...', 'log' => 'تعداد ردیف فایل: ' . count($rows)]);
        $approvedRows = normalizeApprovedSmartReviewRows($rows);
        $result = applySmartPricingRows($legacyConfig, $approvedRows);

        $this->send(['type' => 'progress', 'progress' => 90, 'message' => 'در حال ذخیره خروجی نهایی...', 'log' => 'تعداد ردیف قابل اعمال: ' . count($approvedRows)]);
        saveFinalSmartAppliedRows($legacyConfig, $rows);
        clearSmartReviewSnapshot($legacyConfig, $actorKey);
        $this->service->markJob('smart_review_upload_apply', ($saved['name'] ?? '') . ' | rows=' . count($rows));

        return ['message' => buildSmartApplyResultMessage($result)];
    }

    private function applySavedReview(string $returnPage): array
    {
        $this->send(['type' => 'progress', 'title' => 'اعمال از snapshot ذخیره‌شده', 'progress' => 15, 'message' => 'در حال خواندن snapshot...', 'log' => 'شروع اعمال snapshot']);
        $result = webApplySavedSmartReviewSnapshot($this->service->getLegacyConfig(), Auth::user()['username'] ?? 'admin');
        $this->service->markJob('smart_review_apply_saved', 'rows=' . ($result['rows_count'] ?? 0));
        return ['message' => $result['message']];
    }

    private function buildTextList(string $returnPage): array
    {
        $legacyConfig = $this->service->getLegacyConfig();
        $appConfig = $this->service->getAppConfig();

        $this->send(['type' => 'progress', 'title' => 'بازسازی لیست متنی', 'progress' => 20, 'message' => 'در حال ساخت متن خروجی...', 'log' => 'شروع بازسازی لیست']);
        $text = buildMobilePriceTextList($legacyConfig);
        file_put_contents($appConfig['runtime_path'] . '/text_list_last.txt', $text);
        $this->service->markJob('text_list_build', 'len=' . mb_strlen($text));

        return ['message' => 'لیست متنی ساخته شد.'];
    }



    private function rayanManualCreate(string $returnPage): array
    {
        $this->send(['type' => 'progress', 'title' => 'ثبت فاکتور جدید', 'progress' => 8, 'message' => 'در حال ساخت payload داخلی از فرم دستی...', 'log' => 'شروع ثبت دستی']);
        $rayan = new RayanPanelService();
        $this->send(['type' => 'progress', 'progress' => 35, 'message' => 'در حال ذخیره فرم ثبت فاکتور و تعیین مالک...', 'log' => 'ثبت با نام کاربر فعلی']);
        $result = $rayan->createManualRequest($_POST, false);
        $requestId = (int)($result['request_id'] ?? 0);
        $status = (string)($result['status'] ?? '');
        $message = !empty($result['request_created'])
            ? ('درخواست دستی #' . $requestId . ' ساخته شد و آماده ثبت است.')
            : ((string)($result['message'] ?? 'درخواست دستی ذخیره شد.'));
        $this->send(['type' => 'progress', 'progress' => 95, 'message' => $message, 'log' => 'وضعیت اولیه: ' . ($status !== '' ? $rayan->statusLabel($status) : '-')]);
        return ['message' => $message];
    }

    private function rayanProcess(string $returnPage): array
    {
        $requestId = (int)($_POST['request_id'] ?? 0);
        if ($requestId <= 0) {
            throw new RuntimeException('شناسه درخواست معتبر نیست.');
        }

        $action = (string)($_POST['action'] ?? 'process_request');
        $title = match ($action) {
            'process_stage_one' => 'ثبت مرحله اول',
            'process_stage_two' => 'ثبت مرحله دوم',
            default => 'ثبت درخواست فرم',
        };

        $this->send(['type' => 'progress', 'title' => $title, 'progress' => 8, 'message' => 'در حال آماده‌سازی درخواست...', 'log' => 'درخواست #' . $requestId]);
        $rayan = new RayanPanelService();
        $this->send(['type' => 'progress', 'progress' => 25, 'message' => 'در حال شروع مسیر ثبت...', 'log' => 'اتصال به سرویس مقصد']);

        if ($action === 'process_stage_one') {
            $result = $rayan->processClubStageOne($requestId);
        } elseif ($action === 'process_stage_two') {
            $result = $rayan->processClubStageTwo($requestId);
        } else {
            $result = $rayan->processRequest($requestId);
        }

        $request = $result['request'] ?? [];
        $status = is_array($request) ? (string)($request['status'] ?? '') : '';
        $progress = $status === 'awaiting_otp' ? 90 : 100;
        $this->send(['type' => 'progress', 'progress' => $progress, 'message' => $result['message'], 'log' => 'وضعیت: ' . $rayan->statusLabel($status)]);
        return ['message' => $result['message']];
    }

    private function rayanRetry(string $returnPage): array
    {
        $requestId = (int)($_POST['request_id'] ?? 0);
        if ($requestId <= 0) {
            throw new RuntimeException('شناسه درخواست معتبر نیست.');
        }
        $this->send(['type' => 'progress', 'title' => 'تلاش مجدد ثبت فرم', 'progress' => 8, 'message' => 'در حال پاک‌سازی خطای قبلی...', 'log' => 'درخواست #' . $requestId]);
        $rayan = new RayanPanelService();
        $this->send(['type' => 'progress', 'progress' => 30, 'message' => 'تلاش مجدد شروع شد...', 'log' => 'اجرای دوباره مسیر ثبت']);
        $result = $rayan->retryRequest($requestId);
        $request = $result['request'] ?? [];
        $status = is_array($request) ? (string)($request['status'] ?? '') : '';
        $this->send(['type' => 'progress', 'progress' => $status === 'awaiting_otp' ? 90 : 100, 'message' => $result['message'], 'log' => 'وضعیت: ' . $rayan->statusLabel($status)]);
        return ['message' => $result['message']];
    }

    private function rayanSubmitOtp(string $returnPage): array
    {
        $requestId = (int)($_POST['request_id'] ?? 0);
        $otp = (string)($_POST['otp_code'] ?? '');
        if ($requestId <= 0) {
            throw new RuntimeException('شناسه درخواست معتبر نیست.');
        }
        $this->send(['type' => 'progress', 'title' => 'ارسال کد تایید', 'progress' => 15, 'message' => 'کد تایید در حال ارسال است...', 'log' => 'درخواست #' . $requestId]);
        $rayan = new RayanPanelService();
        $result = $rayan->submitOtp($requestId, $otp);
        $request = $result['request'] ?? [];
        $status = is_array($request) ? (string)($request['status'] ?? '') : '';
        $this->send(['type' => 'progress', 'progress' => 95, 'message' => $result['message'], 'log' => 'وضعیت: ' . $rayan->statusLabel($status)]);
        return ['message' => $result['message']];
    }

    private function rayanInquiry(string $returnPage): array
    {
        $nationalId = (string)($_POST['national_id'] ?? '');
        $mobile = (string)($_POST['mobile'] ?? '');
        $this->send(['type' => 'progress', 'title' => 'استعلام بازنشسته تامین اجتماعی', 'progress' => 10, 'message' => 'در حال ورود به سامانه...', 'log' => 'شروع استعلام']);
        $rayan = new RayanPanelService();
        $this->send(['type' => 'progress', 'progress' => 45, 'message' => 'در حال ارسال اطلاعات مشتری...', 'log' => 'کد ملی و موبایل بررسی می‌شود']);
        $result = $rayan->runCreditInquiry($nationalId, $mobile);
        $rayan->saveCreditInquiryReport($result);
        $_SESSION['rayan_inquiry_result'] = $result;
        $this->send(['type' => 'progress', 'progress' => 90, 'message' => 'نتیجه استعلام دریافت شد.', 'log' => 'سقف اعتبار: ' . (string)($result['credit_limit'] ?? '-')]);
        return ['message' => 'استعلام بازنشسته تامین اجتماعی انجام شد. سقف اعتبار: ' . (string)($result['credit_limit'] ?? '-') . ' | سقف ماهانه: ' . (string)($result['monthly_estimate'] ?? '-')];
    }

    private function buildSmartPricingReviewRowsFromFetchedData(
        array $config,
        array $map,
        array $hesabanRows,
        array $farnaaRows,
        array $hamrahtelRows,
        array $digikalaRows,
        array $mobile140Rows,
        array $technolifeRows,
        array $ahvazRows
    ): array {
        $hesabanIndex    = buildHesabanIndex($hesabanRows);
        $farnaaIndex     = buildFarnaaIndex($farnaaRows);
        $hamrahtelIndex  = buildHamrahtelIndex($hamrahtelRows);
        $digikalaIndex   = buildDigikalaIndex($digikalaRows);
        $mobile140Index  = buildMobile140Index($mobile140Rows);
        $technolifeIndex = buildTechnolifeIndex($technolifeRows);

        $inStockVariantCountsByTitle = buildInStockMappedVariantCountsByTitle($ahvazRows, $map, $hesabanIndex);

        $reviewRows = [];

        foreach ($ahvazRows as $ahvazRow) {
            $productId = (int)($ahvazRow['product_id'] ?? 0);
            if ($productId <= 0) {
                continue;
            }

            $variant = trim((string)($ahvazRow['color'] ?? '-'));
            if ($variant === '') {
                $variant = '-';
            }

            $mapKey = buildAhvazMapRowKey($productId, $variant);

            $mapRow = $map[$mapKey] ?? [
                'product_id'      => $productId,
                'title'           => (string)($ahvazRow['title'] ?? ''),
                'variant'         => $variant,
                'hesabanweb_key'  => '',
                'farnaa_key'      => '',
                'hamrahtel_key'   => '',
                'digikala_key'    => '',
                'mobile140_key'   => '',
                'technolife_key'  => '',
            ];

            $hesabanKey    = normalizeKeyText((string)($mapRow['hesabanweb_key'] ?? ''));
            $farnaaKey     = normalizeKeyText((string)($mapRow['farnaa_key'] ?? ''));
            $hamrahtelKey  = normalizeKeyText((string)($mapRow['hamrahtel_key'] ?? ''));
            $digikalaKey   = normalizeKeyText((string)($mapRow['digikala_key'] ?? ''));
            $mobile140Key  = normalizeKeyText((string)($mapRow['mobile140_key'] ?? ''));
            $technolifeKey = normalizeKeyText((string)($mapRow['technolife_key'] ?? ''));

            $hesabanRow = $hesabanKey !== '' ? ($hesabanIndex[$hesabanKey] ?? null) : null;

            $buyCount = $hesabanRow ? (int)($hesabanRow['count'] ?? 0) : 0;
            $buyPrice = $hesabanRow ? (int)($hesabanRow['buy_price'] ?? 0) : 0;

            $farnaaPrice = ($farnaaKey !== '' && isset($farnaaIndex[$farnaaKey]))
                ? (int)($farnaaIndex[$farnaaKey]['price'] ?? 0)
                : 0;

            $hamrahtelPrice = ($hamrahtelKey !== '' && isset($hamrahtelIndex[$hamrahtelKey]))
                ? (int)($hamrahtelIndex[$hamrahtelKey]['price'] ?? 0)
                : 0;

            $digikalaPrice = ($digikalaKey !== '' && isset($digikalaIndex[$digikalaKey]))
                ? (int)($digikalaIndex[$digikalaKey]['price'] ?? 0)
                : 0;

            $mobile140Price = ($mobile140Key !== '' && isset($mobile140Index[$mobile140Key]))
                ? (int)($mobile140Index[$mobile140Key]['price'] ?? 0)
                : 0;

            $technolifePrice = ($technolifeKey !== '' && isset($technolifeIndex[$technolifeKey]))
                ? (int)($technolifeIndex[$technolifeKey]['price'] ?? 0)
                : 0;

            $primaryMarketPrices = array_values(array_filter([
                $farnaaPrice,
                $mobile140Price,
                $hamrahtelPrice,
            ], fn($x) => (int)$x > 0));

            $secondaryMarketPrices = array_values(array_filter([
                $digikalaPrice,
                $technolifePrice,
            ], fn($x) => (int)$x > 0));

            if (!empty($primaryMarketPrices)) {
                $marketPrices = $primaryMarketPrices;
                $marketSource = 'primary';
            } elseif (!empty($secondaryMarketPrices)) {
                $marketPrices = $secondaryMarketPrices;
                $marketSource = 'secondary';
            } else {
                $marketPrices = [];
                $marketSource = 'none';
            }

            $marketMean = !empty($marketPrices)
                ? (int)round(array_sum($marketPrices) / count($marketPrices))
                : 0;

            $titleKey = normalizeKeyText((string)($ahvazRow['title'] ?? ''));
            $mappedInStockColorCount = count($inStockVariantCountsByTitle[$titleKey] ?? []);
            $hasMultipleMappedInStockColors = $mappedInStockColorCount > 1;

            $markupRate = 1.02;
            if ($hasMultipleMappedInStockColors && isBlackOrWhiteColor($variant)) {
                $markupRate = 1.022;
            }

            $proposedPrice = $marketMean > 0 ? (int)ceil($marketMean * $markupRate) : 0;
            $proposedStock = $buyCount > 0 ? (int)ceil($buyCount / 2) : 0;

            if ($hesabanKey === '' || !$hesabanRow) {
                $status = 'بدون کلید حسابرسی';
                $proposedPrice = 0;
                $proposedStock = 0;
            } elseif ($buyCount <= 0) {
                $status = 'ناموجود در حسابرسی';
                $proposedPrice = 0;
                $proposedStock = 0;
            } elseif ($buyPrice <= 0) {
                $status = 'قیمت خرید نامعتبر';
                $proposedPrice = 0;
            } elseif (empty($marketPrices)) {
                $status = 'بدون مرجع بازار';
                $proposedPrice = 0;
            } elseif ($proposedPrice <= $buyPrice) {
                $status = 'کمتر یا مساوی قیمت خرید';
            } else {
                $status = 'قابل اعمال';
            }

            $reviewRows[] = [
                'product_id'       => $productId,
                'title'            => (string)($ahvazRow['title'] ?? ''),
                'variant'          => $variant,
                'hesabanweb_key'   => (string)($mapRow['hesabanweb_key'] ?? ''),
                'farnaa_key'       => (string)($mapRow['farnaa_key'] ?? ''),
                'hamrahtel_key'    => (string)($mapRow['hamrahtel_key'] ?? ''),
                'digikala_key'     => (string)($mapRow['digikala_key'] ?? ''),
                'mobile140_key'    => (string)($mapRow['mobile140_key'] ?? ''),
                'technolife_key'   => (string)($mapRow['technolife_key'] ?? ''),
                'current_price'    => (int)($ahvazRow['price'] ?? 0),
                'buy_price'        => $buyPrice,
                'buy_count'        => $buyCount,
                'farnaa_price'     => $farnaaPrice,
                'hamrahtel_price'  => $hamrahtelPrice,
                'digikala_price'   => $digikalaPrice,
                'mobile140_price'  => $mobile140Price,
                'technolife_price' => $technolifePrice,
                'market_source'    => $marketSource,
                'market_mean'      => $marketMean,
                'proposed_price'   => $proposedPrice,
                'proposed_stock'   => $proposedStock,
                'status'           => $status,
            ];
        }

        usort($reviewRows, function ($a, $b) {
            return strcmp(
                normalizeKeyText((string)($a['title'] ?? '') . '|' . (string)($a['variant'] ?? '')),
                normalizeKeyText((string)($b['title'] ?? '') . '|' . (string)($b['variant'] ?? ''))
            );
        });

        return $reviewRows;
    }

    private function saveUpload(string $inputName, string $targetPrefix, string $uploadsPath): array
    {
        if (empty($_FILES[$inputName]['tmp_name']) || !is_uploaded_file($_FILES[$inputName]['tmp_name'])) {
            throw new RuntimeException('فایلی انتخاب نشده بود.');
        }

        $original = $_FILES[$inputName]['name'] ?? 'upload.bin';
        $safeName = preg_replace('/[^a-zA-Z0-9._-]+/u', '_', $original);
        $target = $uploadsPath . '/' . time() . '_' . $targetPrefix . '_' . $safeName;

        if (!move_uploaded_file($_FILES[$inputName]['tmp_name'], $target)) {
            throw new RuntimeException('ذخیره فایل آپلودی ممکن نشد.');
        }

        return [
            'path' => $target,
            'name' => basename($target),
        ];
    }

    private function startStream(): void
    {
        @set_time_limit(0);
        @ignore_user_abort(true);
        @ini_set('output_buffering', 'off');
        @ini_set('zlib.output_compression', '0');
        if (function_exists('apache_setenv')) {
            @apache_setenv('no-gzip', '1');
        }

        while (ob_get_level() > 0) {
            @ob_end_flush();
        }

        ob_implicit_flush(true);
        header('Content-Type: application/x-ndjson; charset=utf-8');
        header('Cache-Control: no-cache, no-store, must-revalidate');
        header('Pragma: no-cache');
        header('Expires: 0');
        header('X-Accel-Buffering: no');
    }

    private function send(array $payload): void
    {
        echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n";
        @flush();
    }
}
