<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Core\Auth;
use App\Core\Request;
use App\Core\View;
use App\Repositories\BedsRepository;
use App\Repositories\DoctorsRepository;
use App\Repositories\OperationsRepository;
use App\Repositories\VisitsRepository;
use App\Services\ChartDataService;
use App\Services\FairYearComparisonPolicy;
use DateTimeImmutable;
use Throwable;

final class OperationsController
{
    private function modules(): array
    {
        return [
            'visits' => [
                'icon' => '◇',
                'tone' => 'blue',
                'title_key' => 'operations.visits.title',
                'hero_key' => 'operations.visits.hero_title',
                'text_key' => 'operations.visits.hero_text',
            ],
            'doctors' => [
                'icon' => '✚',
                'tone' => 'green',
                'title_key' => 'operations.doctors.title',
                'hero_key' => 'operations.doctors.hero_title',
                'text_key' => 'operations.doctors.hero_text',
            ],
            'beds' => [
                'icon' => '▣',
                'tone' => 'violet',
                'title_key' => 'operations.beds.title',
                'hero_key' => 'operations.beds.hero_title',
                'text_key' => 'operations.beds.hero_text',
            ],
            'operations' => [
                'icon' => '✦',
                'tone' => 'blue',
                'title_key' => 'operations.operations.title',
                'hero_key' => 'operations.operations.hero_title',
                'text_key' => 'operations.operations.hero_text',
            ],
        ];
    }

    public function module(string $module): void
    {
        Auth::requireLogin();

        $module = strtolower(trim($module));
        $modules = $this->modules();
        if (!array_key_exists($module, $modules)) {
            \redirect('index.php?page=dashboard&lang=' . \current_lang());
        }

        $hospitalCode = Auth::hospital();
        $hospital = \config('hospitals.' . $hospitalCode, []);
        $config = $modules[$module];

        $currentYear = (int) date('Y');
        $yearWasRequested = Request::get('year') !== null;
        $compareYearWasRequested = Request::get('compare_year') !== null;
        $year = \safe_int(Request::get('year'), $currentYear);
        $compareYear = \safe_int(Request::get('compare_year'), $year - 1);
        $metric = strtoupper(trim((string)Request::get('metric', 'TOT_REV')));

        $reportMode = 'year';
        $fromDate = sprintf('%04d-01-01', $year);
        $toDate = sprintf('%04d-03-31', $year);
        $visitsFilterError = null;
        $operationsFilterError = null;
        if (in_array($module, ['visits', 'operations'], true)) {
            $requestedMode = strtolower(trim((string)Request::get('mode', 'year')));
            $reportMode = in_array($requestedMode, ['year', 'range'], true) ? $requestedMode : 'year';
            $fromDate = trim((string)Request::get('from_date', $fromDate));
            $toDate = trim((string)Request::get('to_date', $toDate));

            if ($reportMode === 'range') {
                $from = DateTimeImmutable::createFromFormat('!Y-m-d', $fromDate);
                $fromErrors = DateTimeImmutable::getLastErrors();
                $fromOk = $from instanceof DateTimeImmutable && ($fromErrors === false || ((int)$fromErrors['warning_count'] === 0 && (int)$fromErrors['error_count'] === 0));
                $to = DateTimeImmutable::createFromFormat('!Y-m-d', $toDate);
                $toErrors = DateTimeImmutable::getLastErrors();
                $toOk = $to instanceof DateTimeImmutable && ($toErrors === false || ((int)$toErrors['warning_count'] === 0 && (int)$toErrors['error_count'] === 0));

                if (!$fromOk || !$toOk || $from > $to || $from->format('Y') !== $to->format('Y')) {
                    if ($module === 'operations') {
                        $operationsFilterError = \t('operations.invalid_range');
                    } else {
                        $visitsFilterError = \t('visits.invalid_range');
                    }
                    $reportMode = 'year';
                    $fromDate = sprintf('%04d-01-01', $year);
                    $toDate = sprintf('%04d-03-31', $year);
                } else {
                    $year = (int)$from->format('Y');
                    if (!$compareYearWasRequested) {
                        $compareYear = $year - 1;
                    }
                }
            }
        }

        if ($module === 'doctors') {
            try {
                $yearRepo = new DoctorsRepository($hospitalCode);
                $availableDoctorYears = $yearRepo->availableYears('TOT_REV', 20);
                if (!$yearWasRequested && !empty($availableDoctorYears)) {
                    $year = (int)$availableDoctorYears[0];
                }
                if (!$compareYearWasRequested && !empty($availableDoctorYears)) {
                    $compareYear = $this->defaultCompareYear($availableDoctorYears, $year);
                }
            } catch (Throwable $e) {
                $this->logWorkspaceError('Doctors year auto-detect warning', $e);
            }
        }

        if ($module === 'beds') {
            try {
                $yearRepo = new BedsRepository($hospitalCode);
                $availableBedYears = $yearRepo->availableYears(20);
                if (!$yearWasRequested && !empty($availableBedYears)) {
                    $year = (int)$availableBedYears[0];
                }
                if (!$compareYearWasRequested && !empty($availableBedYears)) {
                    $compareYear = $this->defaultCompareYear($availableBedYears, $year);
                }
            } catch (Throwable $e) {
                $this->logWorkspaceError('Beds year auto-detect warning', $e);
            }
        }

        if ($module === 'operations' && $reportMode === 'year') {
            try {
                $yearRepo = new OperationsRepository($hospitalCode);
                $availableOperationYears = $yearRepo->availableYears(20);
                if (!$yearWasRequested && !empty($availableOperationYears)) {
                    $year = (int)$availableOperationYears[0];
                }
                if (!$compareYearWasRequested && !empty($availableOperationYears)) {
                    $compareYear = $this->defaultCompareYear($availableOperationYears, $year);
                }
                $fromDate = sprintf('%04d-01-01', $year);
                $toDate = sprintf('%04d-03-31', $year);
            } catch (Throwable $e) {
                $this->logWorkspaceError('Operations year auto-detect warning', $e);
            }
        }

        $visitsComparisonContract = [];
        if ($module === 'visits' && $reportMode === 'year') {
            $visitsComparisonContract = (new FairYearComparisonPolicy())->resolve($year, $compareYear);
        }

        $operationsComparisonContract = [];
        if ($module === 'operations' && $reportMode === 'year') {
            $operationsComparisonContract = (new FairYearComparisonPolicy())->resolve($year, $compareYear);
        }

        $stats = [
            ['label' => \t('operations.stat_branch'), 'value' => (string)($hospital['name'] ?? strtoupper($hospitalCode)), 'hint' => \t('operations.stat_branch_hint'), 'tone' => 'blue'],
            ['label' => \t('operations.stat_connection'), 'value' => \t('operations.stat_connection_value'), 'hint' => \t('operations.stat_connection_hint'), 'tone' => 'green'],
            ['label' => \t('operations.stat_security'), 'value' => \t('operations.stat_security_value'), 'hint' => \t('operations.stat_security_hint'), 'tone' => 'violet'],
            ['label' => \t('operations.stat_next'), 'value' => $this->statusValue($module), 'hint' => $this->statusHint($module), 'tone' => 'amber'],
        ];

        if (in_array($module, ['visits', 'operations'], true)) {
            $periodRepo = $module === 'operations' ? OperationsRepository::class : VisitsRepository::class;
            $prefix = $module === 'operations' ? 'operations' : 'visits';
            $currentPeriod = $reportMode === 'range' ? ($fromDate . ' - ' . $toDate) : (string)$year;
            $comparePeriod = $reportMode === 'range'
                ? ($periodRepo::shiftDateToYear($fromDate, $compareYear) . ' - ' . $periodRepo::shiftDateToYear($toDate, $compareYear))
                : (string)$compareYear;

            if (
                $module === 'visits'
                && $reportMode === 'year'
                && !empty($visitsComparisonContract['fair_current_year_comparison'])
            ) {
                $lastCompleteMonth = (int)($visitsComparisonContract['last_complete_month'] ?? 0);
                if ($lastCompleteMonth > 0) {
                    $currentPeriod = $this->monthRangeLabel($year, $lastCompleteMonth);
                    $comparePeriod = $this->monthRangeLabel($compareYear, $lastCompleteMonth);
                } else {
                    $currentPeriod = \current_lang() === 'ar'
                        ? 'لا توجد أشهر مكتملة في ' . $year
                        : 'No completed months in ' . $year;
                    $comparePeriod = \current_lang() === 'ar'
                        ? 'لا توجد أشهر مكتملة قابلة للمقارنة'
                        : 'No comparable completed months';
                }
            }
            $stats = [
                ['label' => \t($prefix . '.stat_period_mode'), 'value' => \t($prefix . '.' . ($reportMode === 'range' ? 'mode_range' : 'mode_year')), 'hint' => \t($prefix . '.stat_period_mode_hint'), 'tone' => 'blue'],
                ['label' => \t($prefix . '.stat_current_period'), 'value' => $currentPeriod, 'hint' => \t($prefix . '.stat_current_period_hint'), 'tone' => 'green'],
                ['label' => \t($prefix . '.stat_compare_period'), 'value' => $comparePeriod, 'hint' => \t($prefix . '.stat_compare_period_hint'), 'tone' => 'violet'],
                ['label' => \t($prefix . '.stat_branch'), 'value' => (string)($hospital['name'] ?? strtoupper($hospitalCode)), 'hint' => \t($prefix . '.stat_branch_hint'), 'tone' => 'amber'],
            ];
        }

        $visitData = null;
        $operationData = null;
        $doctorData = null;
        $bedsData = null;
        $dataError = null;

        if ($module === 'visits') {
            try {
                $visitData = $this->buildVisitsData($hospitalCode, $year, $compareYear, $reportMode, $fromDate, $toDate, $visitsComparisonContract);
            } catch (Throwable $e) {
                $dataError = $e->getMessage();
                $this->logWorkspaceError('Visits workspace error', $e);
            }
        }

        if ($module === 'operations') {
            try {
                $operationData = $this->buildOperationsData(
                    $hospitalCode,
                    $year,
                    $compareYear,
                    $reportMode,
                    $fromDate,
                    $toDate,
                    $operationsComparisonContract
                );
            } catch (Throwable $e) {
                $dataError = $e->getMessage();
                $this->logWorkspaceError('Operations workspace error', $e);
            }
        }

        if ($module === 'doctors') {
            try {
                $doctorData = $this->buildDoctorsData($hospitalCode, $year, $compareYear, $metric);
            } catch (Throwable $e) {
                $dataError = $e->getMessage();
                $this->logWorkspaceError('Doctors workspace error', $e);
            }
        }

        if ($module === 'beds') {
            try {
                $bedsData = $this->buildBedsData($hospitalCode, $year, $compareYear);
            } catch (Throwable $e) {
                $dataError = $e->getMessage();
                $this->logWorkspaceError('Beds workspace error', $e);
            }
        }

        View::render('operations/module', [
            'title' => \t($config['title_key']),
            'activePage' => $module,
            'module' => $module,
            'moduleConfig' => $config,
            'hospital' => $hospital,
            'hospitalCode' => $hospitalCode,
            'stats' => $stats,
            'modules' => $modules,
            'year' => $year,
            'compareYear' => $compareYear,
            'reportMode' => $reportMode,
            'fromDate' => $fromDate,
            'toDate' => $toDate,
            'visitsFilterError' => $visitsFilterError,
            'visitsComparisonContract' => $visitsComparisonContract,
            'operationsFilterError' => $operationsFilterError,
            'operationsComparisonContract' => $operationsComparisonContract,
            'metric' => $metric,
            'visitData' => $visitData,
            'operationData' => $operationData,
            'doctorData' => $doctorData,
            'bedsData' => $bedsData,
            'dataError' => $dataError,
        ]);
    }


    /**
     * @param array<int, int> $years
     */
    private function defaultCompareYear(array $years, int $year): int
    {
        foreach ($years as $candidate) {
            $candidate = (int)$candidate;
            if ($candidate > 0 && $candidate < $year) {
                return $candidate;
            }
        }
        return $year - 1;
    }

    private function statusValue(string $module): string
    {
        if ($module === 'visits') {
            return \t('operations.stat_live_data_value');
        }
        if ($module === 'doctors') {
            return \t('operations.stat_doctors_live_value');
        }
        if ($module === 'beds') {
            return \t('operations.stat_beds_live_value');
        }
        if ($module === 'operations') {
            return \t('operations.stat_operations_live_value');
        }
        return \t('operations.stat_next_value');
    }

    private function statusHint(string $module): string
    {
        if ($module === 'visits') {
            return \t('operations.stat_live_data_hint');
        }
        if ($module === 'doctors') {
            return \t('operations.stat_doctors_live_hint');
        }
        if ($module === 'beds') {
            return \t('operations.stat_beds_live_hint');
        }
        if ($module === 'operations') {
            return \t('operations.stat_operations_live_hint');
        }
        return \t('operations.stat_next_hint');
    }

    private function buildVisitsData(
        string $hospitalCode,
        int $year,
        int $compareYear,
        string $reportMode,
        string $fromDate,
        string $toDate,
        array $comparisonContract = []
    ): array {
        $repo = new VisitsRepository($hospitalCode);
        $chartService = new ChartDataService();
        $warnings = [];
        $emptyMonthly = $repo->emptyMonthlyRows();
        $rangeFrom = $reportMode === 'range' ? $fromDate : null;
        $rangeTo = $reportMode === 'range' ? $toDate : null;
        $isFairCurrentYearComparison = $reportMode === 'year'
            && !empty($comparisonContract['fair_current_year_comparison']);
        $lastCompleteMonth = $isFairCurrentYearComparison
            ? (int)($comparisonContract['last_complete_month'] ?? 0)
            : 12;
        $hasComparableCompletedMonths = !$isFairCurrentYearComparison || $lastCompleteMonth > 0;

        if ($isFairCurrentYearComparison && $hasComparableCompletedMonths) {
            $rangeFrom = isset($comparisonContract['from_date'])
                ? (string)$comparisonContract['from_date']
                : null;
            $rangeTo = isset($comparisonContract['to_date'])
                ? (string)$comparisonContract['to_date']
                : null;
        }

        $monthlyVisitBundle = $hasComparableCompletedMonths
            ? $this->safeDataset(
            'Consolidated OPD and Emergency monthly visits',
            function () use ($repo, $year, $compareYear, $rangeFrom, $rangeTo) {
                return $repo->monthlyVisitBundle($year, $compareYear, $rangeFrom, $rangeTo);
            },
            ['opd' => $emptyMonthly, 'er' => $emptyMonthly],
            $warnings,
            'Visits'
        )
            : ['opd' => [], 'er' => []];
        $opdRows = isset($monthlyVisitBundle['opd']) && is_array($monthlyVisitBundle['opd'])
            ? $monthlyVisitBundle['opd']
            : $emptyMonthly;
        $erRows = isset($monthlyVisitBundle['er']) && is_array($monthlyVisitBundle['er'])
            ? $monthlyVisitBundle['er']
            : $emptyMonthly;

        $admissionRows = $hasComparableCompletedMonths
            ? $this->safeDataset('Admissions monthly visits', function () use ($repo, $year, $compareYear, $rangeFrom, $rangeTo) {
                return $repo->monthlyAdmissions($year, $compareYear, $rangeFrom, $rangeTo);
            }, $emptyMonthly, $warnings, 'Visits')
            : [];

        $followupRows = $hasComparableCompletedMonths
            ? $this->safeDataset('New vs follow-up visits', function () use ($repo, $year, $rangeFrom, $rangeTo) {
                return $repo->followupMix($year, $rangeFrom, $rangeTo);
            }, [], $warnings, 'Visits')
            : [];

        $admissionCategoryRows = $hasComparableCompletedMonths
            ? $this->safeDataset('Admissions by payer category', function () use ($repo, $year, $rangeFrom, $rangeTo) {
                return $repo->admissionCategoryMix($year, $rangeFrom, $rangeTo);
            }, [], $warnings, 'Visits')
            : [];

        $outpatientCashCreditRows = $this->safeDataset('Today outpatient payer mix', function () use ($repo) {
            return $repo->outpatientCashCreditToday();
        }, [], $warnings, 'Visits');

        $dailyRows = $this->safeDataset('Daily visits trend', function () use ($repo) {
            return $repo->dailyOutpatientTrend(14);
        }, [], $warnings, 'Visits');

        $specialties = $isFairCurrentYearComparison
            ? []
            : $this->safeDataset('Top specialties', function () use ($repo, $year) {
                return $repo->topSpecialtiesByOpVisits($year);
            }, [], $warnings, 'Visits');

        $currentPeriodLabel = $reportMode === 'range' ? ($fromDate . ' - ' . $toDate) : (string)$year;
        $comparePeriodLabel = $reportMode === 'range'
            ? (VisitsRepository::shiftDateToYear($fromDate, $compareYear) . ' - ' . VisitsRepository::shiftDateToYear($toDate, $compareYear))
            : (string)$compareYear;

        if ($isFairCurrentYearComparison) {
            if ($lastCompleteMonth > 0) {
                $currentPeriodLabel = $this->monthRangeLabel($year, $lastCompleteMonth);
                $comparePeriodLabel = $this->monthRangeLabel($compareYear, $lastCompleteMonth);
            } else {
                $currentPeriodLabel = \current_lang() === 'ar'
                    ? 'لا توجد أشهر مكتملة في ' . $year
                    : 'No completed months in ' . $year;
                $comparePeriodLabel = \current_lang() === 'ar'
                    ? 'لا توجد أشهر مكتملة قابلة للمقارنة'
                    : 'No comparable completed months';
            }
        }

        $visibleStartMonth = 1;
        $visibleEndMonth = $isFairCurrentYearComparison ? $lastCompleteMonth : 12;
        if ($reportMode === 'range') {
            $visibleStartMonth = (int)(new DateTimeImmutable($fromDate))->format('n');
            $visibleEndMonth = (int)(new DateTimeImmutable($toDate))->format('n');
        }

        $opdRows = $this->filterMonthlyRows($opdRows, $visibleStartMonth, $visibleEndMonth);
        $erRows = $this->filterMonthlyRows($erRows, $visibleStartMonth, $visibleEndMonth);
        $admissionRows = $this->filterMonthlyRows($admissionRows, $visibleStartMonth, $visibleEndMonth);

        $cards = [
            'opd' => $this->metricCardForPeriod(\t('visits.card_opd'), $opdRows, $currentPeriodLabel, $comparePeriodLabel, $chartService),
            'er' => $this->metricCardForPeriod(\t('visits.card_er'), $erRows, $currentPeriodLabel, $comparePeriodLabel, $chartService),
            'admissions' => $this->metricCardForPeriod(\t('visits.card_admissions'), $admissionRows, $currentPeriodLabel, $comparePeriodLabel, $chartService),
        ];

        $followupSummary = $this->followupSummary($followupRows);
        $cards['followup'] = [
            'title' => \t('visits.card_followup'),
            'current_total' => $followupSummary['followup'],
            'compare_total' => $followupSummary['new'],
            'percent' => $followupSummary['ratio'],
            'hint' => \t('visits.followup_hint'),
            'mode' => 'ratio',
        ];

        return [
            'mode' => $reportMode,
            'comparison_contract' => $comparisonContract,
            'current_period_label' => $currentPeriodLabel,
            'compare_period_label' => $comparePeriodLabel,
            'charts' => [
                'opd' => [
                    'title' => \t('visits.chart_opd'),
                    'rows' => $opdRows,
                    'payload' => $chartService->comparePayload($opdRows, \t('visits.chart_opd'), $currentPeriodLabel, $comparePeriodLabel),
                ],
                'er' => [
                    'title' => \t('visits.chart_er'),
                    'rows' => $erRows,
                    'payload' => $chartService->comparePayload($erRows, \t('visits.chart_er'), $currentPeriodLabel, $comparePeriodLabel),
                ],
                'admissions' => [
                    'title' => \t('visits.chart_admissions'),
                    'rows' => $admissionRows,
                    'payload' => $chartService->comparePayload($admissionRows, \t('visits.chart_admissions'), $currentPeriodLabel, $comparePeriodLabel),
                ],
            ],
            'cards' => $cards,
            'followup' => $followupSummary,
            'admissionCategories' => $this->normalizeAdmissionCategories($admissionCategoryRows),
            'outpatientPayerMix' => $this->normalizeAdmissionCategories($outpatientCashCreditRows),
            'daily' => $this->normalizeDailyRows($dailyRows),
            'specialties' => $this->normalizeSpecialties($specialties),
            'warnings' => $warnings,
        ];
    }

    /**
     * @param array<int, array<string, mixed>> $rows
     * @return array<int, array<string, mixed>>
     */
    private function filterMonthlyRows(array $rows, int $startMonth, int $endMonth): array
    {
        if ($endMonth < $startMonth || $endMonth < 1) {
            return [];
        }

        return array_values(array_filter($rows, static function (array $row) use ($startMonth, $endMonth): bool {
            $month = (int)($row['month_id'] ?? 0);
            return $month >= $startMonth && $month <= $endMonth;
        }));
    }

    private function monthRangeLabel(int $year, int $lastCompleteMonth): string
    {
        $en = [1=>'Jan',2=>'Feb',3=>'Mar',4=>'Apr',5=>'May',6=>'Jun',7=>'Jul',8=>'Aug',9=>'Sep',10=>'Oct',11=>'Nov',12=>'Dec'];
        $ar = [1=>'يناير',2=>'فبراير',3=>'مارس',4=>'أبريل',5=>'مايو',6=>'يونيو',7=>'يوليو',8=>'أغسطس',9=>'سبتمبر',10=>'أكتوبر',11=>'نوفمبر',12=>'ديسمبر'];
        $months = \current_lang() === 'ar' ? $ar : $en;

        return ($months[1] ?? 'Jan') . '–' . ($months[$lastCompleteMonth] ?? (string)$lastCompleteMonth) . ' ' . $year;
    }

    private function buildOperationsData(
        string $hospitalCode,
        int $year,
        int $compareYear,
        string $reportMode,
        string $fromDate,
        string $toDate,
        array $comparisonContract = []
    ): array {
        $repo = new OperationsRepository($hospitalCode);
        $chartService = new ChartDataService();
        $warnings = [];
        $emptyMonthly = $repo->emptyMonthlyRows();

        $isFairCurrentYearComparison = $reportMode === 'year'
            && !empty($comparisonContract['fair_current_year_comparison']);
        $lastCompleteMonth = $isFairCurrentYearComparison
            ? (int)($comparisonContract['last_complete_month'] ?? 0)
            : 12;
        $hasComparableMonths = !$isFairCurrentYearComparison || $lastCompleteMonth > 0;

        $rangeFrom = $reportMode === 'range' ? $fromDate : null;
        $rangeTo = $reportMode === 'range' ? $toDate : null;

        if ($isFairCurrentYearComparison && $hasComparableMonths) {
            $rangeFrom = isset($comparisonContract['from_date'])
                ? (string)$comparisonContract['from_date']
                : sprintf('%04d-01-01', $year);
            $rangeTo = isset($comparisonContract['to_date'])
                ? (string)$comparisonContract['to_date']
                : sprintf('%04d-%02d-01', $year, $lastCompleteMonth);
        }

        $compareRangeFrom = $rangeFrom !== null
            ? OperationsRepository::shiftDateToYear($rangeFrom, $compareYear)
            : null;
        $compareRangeTo = $rangeTo !== null
            ? OperationsRepository::shiftDateToYear($rangeTo, $compareYear)
            : null;

        $scheduledRows = $hasComparableMonths
            ? $this->safeDataset('Scheduled operations', function () use ($repo, $year, $compareYear, $rangeFrom, $rangeTo) {
                return $repo->monthlyLegacyOperations($year, $compareYear, $rangeFrom, $rangeTo);
            }, $emptyMonthly, $warnings, 'Operations')
            : [];

        $performedRows = $hasComparableMonths
            ? $this->safeDataset('Performed operations', function () use ($repo, $year, $compareYear, $rangeFrom, $rangeTo) {
                return $repo->monthlyPerformedOperations($year, $compareYear, $rangeFrom, $rangeTo);
            }, $emptyMonthly, $warnings, 'Operations')
            : [];

        $cancelledRows = $hasComparableMonths
            ? $this->safeDataset('Cancelled operations', function () use ($repo, $year, $compareYear, $rangeFrom, $rangeTo) {
                return $repo->monthlyCancelledOperations($year, $compareYear, $rangeFrom, $rangeTo);
            }, $emptyMonthly, $warnings, 'Operations')
            : [];

        $visibleStartMonth = 1;
        $visibleEndMonth = 12;
        if ($reportMode === 'range') {
            $visibleStartMonth = (int)(new DateTimeImmutable($fromDate))->format('n');
            $visibleEndMonth = (int)(new DateTimeImmutable($toDate))->format('n');
        } elseif ($isFairCurrentYearComparison) {
            $visibleEndMonth = $lastCompleteMonth;
        }

        $scheduledRows = $this->filterMonthlyRows($scheduledRows, $visibleStartMonth, $visibleEndMonth);
        $performedRows = $this->filterMonthlyRows($performedRows, $visibleStartMonth, $visibleEndMonth);
        $cancelledRows = $this->filterMonthlyRows($cancelledRows, $visibleStartMonth, $visibleEndMonth);

        if ($reportMode === 'range') {
            $currentPeriodLabel = $fromDate . ' - ' . $toDate;
            $comparePeriodLabel = $compareRangeFrom . ' - ' . $compareRangeTo;
        } elseif ($isFairCurrentYearComparison) {
            $currentPeriodLabel = $lastCompleteMonth > 0
                ? $this->monthRangeLabel($year, $lastCompleteMonth)
                : (\current_lang() === 'ar' ? 'لا توجد أشهر مكتملة في ' . $year : 'No completed months in ' . $year);
            $comparePeriodLabel = $lastCompleteMonth > 0
                ? $this->monthRangeLabel($compareYear, $lastCompleteMonth)
                : (\current_lang() === 'ar' ? 'لا توجد أشهر قابلة للمقارنة' : 'No comparable completed months');
        } else {
            $currentPeriodLabel = (string)$year;
            $comparePeriodLabel = (string)$compareYear;
        }

        $scheduledCurrent = $chartService->total($scheduledRows, 'current_value');
        $scheduledCompare = $chartService->total($scheduledRows, 'compare_value');
        $performedCurrent = $chartService->total($performedRows, 'current_value');
        $performedCompare = $chartService->total($performedRows, 'compare_value');
        $cancelledCurrent = $chartService->total($cancelledRows, 'current_value');
        $cancelledCompare = $chartService->total($cancelledRows, 'compare_value');
        $completionCurrent = $scheduledCurrent > 0 ? ($performedCurrent / $scheduledCurrent) * 100 : null;
        $completionCompare = $scheduledCompare > 0 ? ($performedCompare / $scheduledCompare) * 100 : null;
        $cancellationRateCurrent = ($scheduledCurrent + $cancelledCurrent) > 0
            ? ($cancelledCurrent / ($scheduledCurrent + $cancelledCurrent)) * 100
            : null;
        $cancellationRateCompare = ($scheduledCompare + $cancelledCompare) > 0
            ? ($cancelledCompare / ($scheduledCompare + $cancelledCompare)) * 100
            : null;

        $currentSpecialties = $hasComparableMonths
            ? $this->safeDataset('Current operation specialties', function () use ($repo, $year, $rangeFrom, $rangeTo) {
                return $repo->topSpecialties($year, $rangeFrom, $rangeTo, 25);
            }, [], $warnings, 'Operations')
            : [];
        $compareSpecialties = $hasComparableMonths
            ? $this->safeDataset('Comparison operation specialties', function () use ($repo, $compareYear, $compareRangeFrom, $compareRangeTo) {
                return $repo->topSpecialties($compareYear, $compareRangeFrom, $compareRangeTo, 25);
            }, [], $warnings, 'Operations')
            : [];

        $currentDoctors = $hasComparableMonths
            ? $this->safeDataset('Current operation doctors', function () use ($repo, $year, $rangeFrom, $rangeTo) {
                return $repo->topDoctors($year, $rangeFrom, $rangeTo, 25);
            }, [], $warnings, 'Operations')
            : [];
        $compareDoctors = $hasComparableMonths
            ? $this->safeDataset('Comparison operation doctors', function () use ($repo, $compareYear, $compareRangeFrom, $compareRangeTo) {
                return $repo->topDoctors($compareYear, $compareRangeFrom, $compareRangeTo, 25);
            }, [], $warnings, 'Operations')
            : [];

        $currentRooms = $hasComparableMonths
            ? $this->safeDataset('Current operating rooms', function () use ($repo, $year, $rangeFrom, $rangeTo) {
                return $repo->topOperatingRooms($year, $rangeFrom, $rangeTo, 25);
            }, [], $warnings, 'Operations')
            : [];
        $compareRooms = $hasComparableMonths
            ? $this->safeDataset('Comparison operating rooms', function () use ($repo, $compareYear, $compareRangeFrom, $compareRangeTo) {
                return $repo->topOperatingRooms($compareYear, $compareRangeFrom, $compareRangeTo, 25);
            }, [], $warnings, 'Operations')
            : [];

        $availableYears = $this->safeDataset('Operations available years', function () use ($repo) {
            return $repo->availableYears(20);
        }, [], $warnings, 'Operations');
        $availableYears = is_array($availableYears) ? array_map('intval', $availableYears) : [];
        foreach ([$year, $compareYear, (int)date('Y')] as $requiredYear) {
            if ($requiredYear > 0 && !in_array($requiredYear, $availableYears, true)) {
                $availableYears[] = $requiredYear;
            }
        }
        $availableYears = array_values(array_unique($availableYears));
        rsort($availableYears, SORT_NUMERIC);

        $cards = [
            'scheduled' => $this->metricCardForPeriod(\t('operations.card_scheduled'), $scheduledRows, $currentPeriodLabel, $comparePeriodLabel, $chartService),
            'performed' => $this->metricCardForPeriod(\t('operations.card_performed'), $performedRows, $currentPeriodLabel, $comparePeriodLabel, $chartService),
            'cancelled' => $this->metricCardForPeriod(\t('operations.card_cancelled'), $cancelledRows, $currentPeriodLabel, $comparePeriodLabel, $chartService),
            'completion' => [
                'title' => \t('operations.card_completion'),
                'current_total' => $completionCurrent,
                'compare_total' => $completionCompare,
                'percent' => ($completionCurrent !== null && $completionCompare !== null)
                    ? ($completionCurrent - $completionCompare)
                    : null,
                'hint' => \t('operations.completion_hint'),
                'mode' => 'percent',
            ],
        ];

        return [
            'mode' => $reportMode,
            'comparison_contract' => $comparisonContract,
            'current_period_label' => $currentPeriodLabel,
            'compare_period_label' => $comparePeriodLabel,
            'visible_start_month' => $visibleStartMonth,
            'visible_end_month' => $visibleEndMonth,
            'cards' => $cards,
            'summary' => [
                'scheduled_current' => $scheduledCurrent,
                'scheduled_compare' => $scheduledCompare,
                'performed_current' => $performedCurrent,
                'performed_compare' => $performedCompare,
                'cancelled_current' => $cancelledCurrent,
                'cancelled_compare' => $cancelledCompare,
                'completion_rate_current' => $completionCurrent,
                'completion_rate_compare' => $completionCompare,
                'cancellation_rate_current' => $cancellationRateCurrent,
                'cancellation_rate_compare' => $cancellationRateCompare,
            ],
            'charts' => [
                'scheduled' => [
                    'title' => \t('operations.chart_scheduled'),
                    'rows' => $scheduledRows,
                    'payload' => $chartService->comparePayload($scheduledRows, \t('operations.chart_scheduled'), $currentPeriodLabel, $comparePeriodLabel),
                ],
                'performed' => [
                    'title' => \t('operations.chart_performed'),
                    'rows' => $performedRows,
                    'payload' => $chartService->comparePayload($performedRows, \t('operations.chart_performed'), $currentPeriodLabel, $comparePeriodLabel),
                ],
                'cancelled' => [
                    'title' => \t('operations.chart_cancelled'),
                    'rows' => $cancelledRows,
                    'payload' => $chartService->comparePayload($cancelledRows, \t('operations.chart_cancelled'), $currentPeriodLabel, $comparePeriodLabel),
                ],
            ],
            'specialtyComparisons' => $this->mergeOperationRankComparisons($currentSpecialties, $compareSpecialties),
            'doctorComparisons' => $this->mergeOperationRankComparisons($currentDoctors, $compareDoctors),
            'roomComparisons' => $this->mergeOperationRankComparisons($currentRooms, $compareRooms),
            'availableYears' => $availableYears,
            'comparison_ready' => $scheduledCurrent > 0 && $scheduledCompare > 0,
            'warnings' => $warnings,
        ];
    }

    /**
     * @param array<int,array<string,mixed>> $currentRows
     * @param array<int,array<string,mixed>> $compareRows
     * @return array<int,array{label:string,current_value:float,compare_value:float,delta_percent:?float}>
     */
    private function mergeOperationRankComparisons(array $currentRows, array $compareRows): array
    {
        $merged = [];

        foreach ($compareRows as $row) {
            $label = trim((string)($row['label'] ?? ''));
            if ($label === '') {
                continue;
            }
            $key = mb_strtolower($label);
            $merged[$key] = [
                'label' => $label,
                'current_value' => 0.0,
                'compare_value' => (float)($row['value'] ?? 0),
                'delta_percent' => null,
            ];
        }

        foreach ($currentRows as $row) {
            $label = trim((string)($row['label'] ?? ''));
            if ($label === '') {
                continue;
            }
            $key = mb_strtolower($label);
            if (!isset($merged[$key])) {
                $merged[$key] = [
                    'label' => $label,
                    'current_value' => 0.0,
                    'compare_value' => 0.0,
                    'delta_percent' => null,
                ];
            }
            $merged[$key]['label'] = $label;
            $merged[$key]['current_value'] = (float)($row['value'] ?? 0);
        }

        foreach ($merged as &$row) {
            $compare = (float)$row['compare_value'];
            $current = (float)$row['current_value'];
            $row['delta_percent'] = abs($compare) > 0.000001
                ? (($current - $compare) / abs($compare)) * 100
                : null;
            $row['volume_change'] = $current - $compare;
            $row['current_rank_eligible'] = $current > 0.000001;
            $row['zero_current_loss'] = $current <= 0.000001 && $compare > 0.000001;
        }
        unset($row);

        $rows = array_values($merged);
        usort($rows, static function (array $a, array $b): int {
            $currentOrder = ((float)$b['current_value']) <=> ((float)$a['current_value']);
            if ($currentOrder !== 0) {
                return $currentOrder;
            }

            $compareOrder = ((float)$b['compare_value']) <=> ((float)$a['compare_value']);
            if ($compareOrder !== 0) {
                return $compareOrder;
            }

            return strcasecmp((string)$a['label'], (string)$b['label']);
        });

        return $rows;
    }

    private function buildDoctorsData(string $hospitalCode, int $year, int $compareYear, string $metric): array
    {
        $repo = new DoctorsRepository($hospitalCode);
        $chartService = new ChartDataService();
        $warnings = [];

        $availableMetrics = $repo->availableMetrics();
        $allowed = array_map(function (array $row): string { return (string)$row['key']; }, $availableMetrics);
        if (!in_array($metric, $allowed, true)) {
            $metric = in_array('TOT_REV', $allowed, true) ? 'TOT_REV' : (string)($allowed[0] ?? 'TOT_REV');
        }

        $availableYears = $this->safeDataset('Doctor available years', function () use ($repo, $metric) {
            return $repo->availableYears($metric, 20);
        }, [], $warnings, 'Doctors');
        $availableYears = is_array($availableYears) ? array_map('intval', $availableYears) : [];
        if (!empty($availableYears) && !in_array($year, $availableYears, true)) {
            $warnings[] = \t('doctors.no_data_for_selected_year', ['year' => $year, 'latest' => (int)$availableYears[0]]);
        }

        $summary = $this->safeDataset('Doctor summary', function () use ($repo, $year, $compareYear) {
            return $repo->summary($year, $compareYear);
        }, [], $warnings, 'Doctors');

        $topDoctors = $this->safeDataset('Top doctors', function () use ($repo, $year, $metric) {
            return $repo->topDoctors($year, $metric, 10);
        }, [], $warnings, 'Doctors');

        $specialties = $this->safeDataset('Specialty contribution', function () use ($repo, $year, $metric) {
            return $repo->specialties($year, $metric, 10);
        }, [], $warnings, 'Doctors');

        $mix = $this->safeDataset('Doctor revenue mix', function () use ($repo, $year) {
            return $repo->revenueMix($year);
        }, [], $warnings, 'Doctors');

        $summary = is_array($summary) ? $summary : [];
        $cards = [
            'revenue' => $this->doctorMetricCard(\t('doctors.card_revenue'), (float)($summary['current_revenue'] ?? 0), (float)($summary['compare_revenue'] ?? 0), $year, $compareYear, $chartService),
            'opd' => $this->doctorMetricCard(\t('doctors.card_opd'), (float)($summary['current_op_visits'] ?? 0), (float)($summary['compare_op_visits'] ?? 0), $year, $compareYear, $chartService),
            'inpatient' => $this->doctorMetricCard(\t('doctors.card_inpatient'), (float)($summary['current_inpat_visits'] ?? 0), (float)($summary['compare_inpat_visits'] ?? 0), $year, $compareYear, $chartService),
            'active' => [
                'title' => \t('doctors.card_active'),
                'current_total' => (float)($summary['active_doctors'] ?? 0),
                'compare_total' => (float)($summary['current_moh'] ?? 0),
                'percent' => null,
                'hint' => \t('doctors.active_hint'),
                'mode' => 'single',
            ],
        ];

        return [
            'summary' => $summary,
            'cards' => $cards,
            'topDoctors' => $topDoctors,
            'specialties' => $specialties,
            'mix' => $mix,
            'metric' => $metric,
            'availableMetrics' => $availableMetrics,
            'availableYears' => $availableYears,
            'warnings' => $warnings,
            'topDoctorsPayload' => $this->singleSeriesPayload(\t('doctors.top_doctors_title'), $topDoctors, $this->metricLabel($availableMetrics, $metric)),
            'specialtiesPayload' => $this->singleSeriesPayload(\t('doctors.specialty_title'), $specialties, $this->metricLabel($availableMetrics, $metric)),
        ];
    }


    private function buildBedsData(string $hospitalCode, int $year, int $compareYear): array
    {
        $repo = new BedsRepository($hospitalCode);
        $chartService = new ChartDataService();
        $warnings = [];

        $availableYears = $this->safeDataset('Beds available years', function () use ($repo) {
            return $repo->availableYears(20);
        }, [], $warnings, 'Beds');
        $availableYears = is_array($availableYears) ? array_map('intval', $availableYears) : [];
        if (!empty($availableYears) && !in_array($year, $availableYears, true)) {
            $warnings[] = \t('beds.no_data_for_selected_year', ['year' => $year, 'latest' => (int)$availableYears[0]]);
        }

        $current = $this->safeDataset('Current bed occupancy', function () use ($repo) {
            return $repo->currentOccupancy();
        }, [], $warnings, 'Beds');

        $annual = $this->safeDataset('Annual bed occupancy hours', function () use ($repo, $year, $compareYear) {
            return $repo->annualSummary($year, $compareYear);
        }, [], $warnings, 'Beds');

        $units = $this->safeDataset('Unit occupancy', function () use ($repo, $year) {
            return $repo->unitOccupancy($year, 12);
        }, [], $warnings, 'Beds');

        $topBeds = $this->safeDataset('Top beds occupancy', function () use ($repo, $year) {
            return $repo->topBeds($year, 15);
        }, [], $warnings, 'Beds');

        $current = is_array($current) ? $current : [];
        $annual = is_array($annual) ? $annual : [];
        $units = is_array($units) ? $units : [];
        $topBeds = is_array($topBeds) ? $topBeds : [];

        $cards = [
            'occupancy' => [
                'title' => \t('beds.card_occupancy'),
                'current_total' => (float)($current['occupancy_rate'] ?? 0),
                'compare_total' => (float)($annual['compare_efficiency'] ?? 0),
                'percent' => null,
                'hint' => \t('beds.current_rate_hint'),
                'mode' => 'percent',
            ],
            'occupied' => [
                'title' => \t('beds.card_occupied'),
                'current_total' => (float)($current['occupied_beds'] ?? 0),
                'compare_total' => (float)($current['total_beds'] ?? 0),
                'percent' => null,
                'hint' => \t('beds.total_beds_hint', ['total' => number_format((float)($current['total_beds'] ?? 0), 0)]),
                'mode' => 'single',
            ],
            'available' => [
                'title' => \t('beds.card_available'),
                'current_total' => (float)($current['available_beds'] ?? 0),
                'compare_total' => 0.0,
                'percent' => null,
                'hint' => \t('beds.available_hint'),
                'mode' => 'single',
            ],
            'hours' => $this->doctorMetricCard(\t('beds.card_hours'), (float)($annual['current_hours'] ?? 0), (float)($annual['compare_hours'] ?? 0), $year, $compareYear, $chartService),
        ];

        return [
            'current' => $current,
            'annual' => $annual,
            'cards' => $cards,
            'units' => $units,
            'topBeds' => $topBeds,
            'availableYears' => $availableYears,
            'warnings' => $warnings,
            'unitsPayload' => $this->singleSeriesPayload(\t('beds.unit_chart_title'), $units, \t('beds.occupancy_percent')),
            'topBedsPayload' => $this->singleSeriesPayload(\t('beds.top_beds_title'), $topBeds, \t('beds.occupancy_percent')),
        ];
    }

    /**
     * @param callable(): mixed $callback
     * @param mixed $fallback
     * @param array<int, string> $warnings
     * @return mixed
     */
    private function safeDataset(string $label, callable $callback, $fallback, array &$warnings, string $scope)
    {
        try {
            return $callback();
        } catch (Throwable $e) {
            $warnings[] = $label . ': ' . $e->getMessage();
            $this->logWorkspaceError($scope . ' dataset warning [' . $label . ']', $e);
            return $fallback;
        }
    }

    private function logWorkspaceError(string $label, Throwable $e): void
    {
        $logDir = \storage_path('logs');
        if (!is_dir($logDir)) {
            @mkdir($logDir, 0775, true);
        }
        @error_log('[' . date('c') . '] ' . $label . ': ' . $e . PHP_EOL, 3, $logDir . '/app.log');
    }

    private function metricCard(string $title, array $rows, int $year, int $compareYear, ChartDataService $chartService): array
    {
        $current = $chartService->total($rows, 'current_value');
        $compare = $chartService->total($rows, 'compare_value');
        return [
            'title' => $title,
            'current_total' => $current,
            'compare_total' => $compare,
            'percent' => $chartService->percentChange($current, $compare),
            'hint' => $year . ' ' . \t('visits.vs') . ' ' . $compareYear,
            'mode' => 'compare',
        ];
    }

    private function metricCardForPeriod(
        string $title,
        array $rows,
        string $currentPeriod,
        string $comparePeriod,
        ChartDataService $chartService
    ): array {
        $current = $chartService->total($rows, 'current_value');
        $compare = $chartService->total($rows, 'compare_value');
        return [
            'title' => $title,
            'current_total' => $current,
            'compare_total' => $compare,
            'percent' => $chartService->percentChange($current, $compare),
            'hint' => $currentPeriod . ' ' . \t('visits.vs') . ' ' . $comparePeriod,
            'mode' => 'compare',
        ];
    }

    private function doctorMetricCard(string $title, float $current, float $compare, int $year, int $compareYear, ChartDataService $chartService): array
    {
        return [
            'title' => $title,
            'current_total' => $current,
            'compare_total' => $compare,
            'percent' => $chartService->percentChange($current, $compare),
            'hint' => $year . ' ' . \t('visits.vs') . ' ' . $compareYear,
            'mode' => 'compare',
        ];
    }

    private function followupSummary(array $rows): array
    {
        $new = 0.0;
        $followup = 0.0;
        foreach ($rows as $row) {
            $category = strtolower((string)($row['category'] ?? ''));
            if ($category === 'followup') {
                $followup += (float)($row['value'] ?? 0);
            } else {
                $new += (float)($row['value'] ?? 0);
            }
        }
        $total = $new + $followup;
        return [
            'new' => $new,
            'followup' => $followup,
            'total' => $total,
            'ratio' => $total > 0 ? ($followup / $total) * 100 : null,
            'new_ratio' => $total > 0 ? ($new / $total) * 100 : null,
        ];
    }

    /**
     * @param array<int, array<string, mixed>> $rows
     * @return array<int, array{category:string,value:float,percent:float}>
     */
    private function normalizeAdmissionCategories(array $rows): array
    {
        $total = 0.0;
        foreach ($rows as $row) {
            $total += (float)($row['value'] ?? 0);
        }

        return array_map(static function (array $row) use ($total): array {
            $value = (float)($row['value'] ?? 0);
            return [
                'category' => strtolower(trim((string)($row['category'] ?? 'other'))),
                'value' => $value,
                'percent' => $total > 0 ? ($value / $total) * 100 : 0.0,
            ];
        }, $rows);
    }

    private function normalizeDailyRows(array $rows): array
    {
        return array_map(function (array $row): array {
            return [
                'label' => trim((string)($row['day_label'] ?? '')),
                'value' => (float)($row['value'] ?? 0),
            ];
        }, $rows);
    }

    private function normalizeSpecialties(array $rows): array
    {
        return array_map(function (array $row): array {
            return [
                'label' => trim((string)($row['specialty_name'] ?? $row['label'] ?? '')),
                'value' => (float)($row['value'] ?? 0),
            ];
        }, $rows);
    }

    /** @return array<int, array{label:string,value:float}> */
    private function normalizeOperationRanks(array $rows, string $labelKey): array
    {
        return array_map(static function (array $row) use ($labelKey): array {
            return [
                'label' => trim((string)($row[$labelKey] ?? $row['label'] ?? '')),
                'value' => (float)($row['value'] ?? 0),
            ];
        }, $rows);
    }

    private function metricLabel(array $availableMetrics, string $metric): string
    {
        foreach ($availableMetrics as $row) {
            if (($row['key'] ?? '') === $metric) {
                return \t((string)$row['label_key']);
            }
        }
        return \t('doctors.metric_revenue');
    }

    private function singleSeriesPayload(string $title, array $rows, string $label): array
    {
        return [
            'title' => $title,
            'labels' => array_map(function (array $row): string {
                return mb_substr(trim((string)($row['label'] ?? '')), 0, 24);
            }, $rows),
            'series' => [[
                'label' => $label,
                'values' => array_map(function (array $row): float {
                    return (float)($row['value'] ?? 0);
                }, $rows),
            ]],
        ];
    }
}
