<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Core\Auth;
use App\Core\BranchReadiness;
use App\Core\Request;
use App\Core\View;
use App\Repositories\BranchComparisonRepository;
use App\Services\FairYearComparisonPolicy;
use DateTimeImmutable;
use Throwable;

final class BranchComparisonController
{
    /** @var array<string, array<string, string>> */
    private const METRICS = [
        'outpatient' => [
            'en' => 'Outpatient visits',
            'ar' => 'الزيارات الخارجية',
            'short_en' => 'Outpatient',
            'short_ar' => 'الخارجي',
        ],
        'admissions' => [
            'en' => 'Patient admissions',
            'ar' => 'دخول المرضى',
            'short_en' => 'Admissions',
            'short_ar' => 'الدخول',
        ],
        'emergency' => [
            'en' => 'Emergency visits',
            'ar' => 'زيارات الطوارئ',
            'short_en' => 'Emergency',
            'short_ar' => 'الطوارئ',
        ],
        'operations' => [
            'en' => 'Scheduled operations',
            'ar' => 'العمليات المجدولة',
            'short_en' => 'Operations',
            'short_ar' => 'العمليات',
        ],
        'laboratory' => [
            'en' => 'Laboratory tests',
            'ar' => 'فحوصات المختبر',
            'short_en' => 'Laboratory',
            'short_ar' => 'المختبر',
        ],
        'radiology' => [
            'en' => 'Radiology tests',
            'ar' => 'فحوصات الأشعة',
            'short_en' => 'Radiology',
            'short_ar' => 'الأشعة',
        ],
    ];

    public function index(): void
    {
        Auth::requireLogin();

        $currentYear = (int)date('Y');
        $year = \safe_int(Request::get('year'), $currentYear);
        $compareYear = \safe_int(Request::get('compare_year'), $year - 1);
        $requestedMode = strtolower(trim((string)Request::get('mode', 'year')));
        $reportMode = in_array($requestedMode, ['year', 'range'], true)
            ? $requestedMode
            : 'year';
        $fromDate = trim((string)Request::get('from_date', sprintf('%04d-01-01', $year)));
        $toDate = trim((string)Request::get('to_date', date('Y-m-d')));
        $filterError = null;

        if ($compareYear === $year) {
            $compareYear = $year - 1;
            $filterError = current_lang() === 'ar'
                ? 'يجب أن تختلف سنة المقارنة عن السنة الأساسية.'
                : 'The comparison year must differ from the primary year.';
        }

        if ($reportMode === 'range') {
            $from = $this->strictDate($fromDate);
            $to = $this->strictDate($toDate);

            if (
                !$from instanceof DateTimeImmutable
                || !$to instanceof DateTimeImmutable
                || $from > $to
                || $from->format('Y') !== $to->format('Y')
            ) {
                $filterError = current_lang() === 'ar'
                    ? 'يجب أن تكون الفترة صحيحة وداخل سنة تقويمية واحدة.'
                    : 'The custom period must be valid and remain within one calendar year.';
                $reportMode = 'year';
                $fromDate = sprintf('%04d-01-01', $year);
                $toDate = date('Y-m-d');
            } else {
                $year = (int)$from->format('Y');
            }
        }

        $policy = new FairYearComparisonPolicy();
        $comparisonContract = $reportMode === 'year'
            ? $policy->resolve($year, $compareYear)
            : [];

        $periods = $this->resolvePeriods(
            $reportMode,
            $year,
            $compareYear,
            $fromDate,
            $toDate,
            $comparisonContract
        );

        $availableYears = $policy->availableYears(2014);
        foreach ([$year, $compareYear] as $requiredYear) {
            if ($requiredYear > 0 && !in_array($requiredYear, $availableYears, true)) {
                $availableYears[] = $requiredYear;
            }
        }
        $availableYears = array_values(array_unique(array_map('intval', $availableYears)));
        rsort($availableYears, SORT_NUMERIC);

        $branches = BranchReadiness::liveCodes();
        $branchDefinitions = [];
        foreach ($branches as $branchCode) {
            $hospital = \config('hospitals.' . $branchCode, []);
            $branchDefinitions[$branchCode] = [
                'code' => $branchCode,
                'short_name' => strtoupper($branchCode),
                'name' => (string)($hospital['name'] ?? strtoupper($branchCode)),
            ];
        }

        $comparison = null;
        $dataError = null;

        try {
            $comparison = $this->buildComparison(
                $branches,
                $branchDefinitions,
                $periods
            );
        } catch (Throwable $error) {
            $dataError = $error->getMessage();
            error_log('Branch comparison error: ' . $error->getMessage());
        }

        View::render('branch_comparison/index', [
            'title' => current_lang() === 'ar' ? 'مقارنة الفروع' : 'Branch Comparison',
            'activePage' => 'branch-comparison',
            'year' => $year,
            'compareYear' => $compareYear,
            'reportMode' => $reportMode,
            'fromDate' => $fromDate,
            'toDate' => $toDate,
            'availableYears' => $availableYears,
            'comparisonContract' => $comparisonContract,
            'periods' => $periods,
            'branchDefinitions' => $branchDefinitions,
            'comparison' => $comparison,
            'filterError' => $filterError,
            'dataError' => $dataError,
        ]);
    }

    /**
     * @param array<int, string> $branches
     * @param array<string, array<string, string>> $branchDefinitions
     * @param array<string, mixed> $periods
     * @return array<string, mixed>
     */
    private function buildComparison(
        array $branches,
        array $branchDefinitions,
        array $periods
    ): array {
        if (count($branches) < 2) {
            throw new \RuntimeException(
                'At least two approved branches are required for Branch Comparison.'
            );
        }

        $repo = new BranchComparisonRepository();
        $raw = [];
        $errors = [];

        foreach ($branches as $branchCode) {
            try {
                $raw[$branchCode] = [
                    'current' => $repo->periodRows(
                        $branchCode,
                        (string)$periods['current_from'],
                        (string)$periods['current_to']
                    ),
                    'compare' => $repo->periodRows(
                        $branchCode,
                        (string)$periods['compare_from'],
                        (string)$periods['compare_to']
                    ),
                ];
            } catch (Throwable $error) {
                $technicalMessage = $this->safeTechnicalError($error->getMessage());
                error_log(
                    'Branch Comparison source failure branch='
                    . strtoupper($branchCode)
                    . ' error='
                    . $technicalMessage
                );

                $errors[$branchCode] = [
                    'branch' => $branchCode,
                    'code' => $this->oracleErrorCode($technicalMessage),
                    'message' => current_lang() === 'ar'
                        ? 'تعذر إكمال المقارنة لأن بيانات هذا الفرع غير متاحة حاليًا.'
                        : 'The comparison could not be completed because data for this branch is currently unavailable.',
                ];
            }
        }

        // No partial rendering: one failed required branch blocks the comparison.
        if ($errors !== []) {
            return [
                'ready' => false,
                'branches' => $branchDefinitions,
                'errors' => $errors,
                'metrics' => [],
                'branch_totals' => [],
                'summary' => [],
                'access_paths' => $repo->accessPaths(),
                'connectivity' => $repo->connectivityDiagnostics(),
                'oracle_writes' => 'NONE',
            ];
        }

        $indexed = [];
        foreach ($raw as $branchCode => $periodRows) {
            foreach (['current', 'compare'] as $periodKey) {
                foreach ($periodRows[$periodKey] as $row) {
                    $metric = strtolower(trim((string)($row['metric_key'] ?? '')));
                    $month = (int)($row['month_id'] ?? 0);
                    if (!isset(self::METRICS[$metric]) || $month < 1 || $month > 12) {
                        continue;
                    }

                    $indexed[$branchCode][$periodKey][$metric][$month] = [
                        'month_name' => trim((string)($row['month_name'] ?? '')),
                        'value' => (float)($row['value'] ?? 0),
                    ];
                }
            }
        }

        $metrics = [];
        $visibleStartMonth = (int)$periods['visible_start_month'];
        $visibleEndMonth = (int)$periods['visible_end_month'];

        foreach (self::METRICS as $metricKey => $metricLabels) {
            $rows = [];
            $totals = [];
            $series = [];

            foreach ($branches as $branchCode) {
                $totals[$branchCode] = [
                    'current' => 0.0,
                    'compare' => 0.0,
                    'movement' => null,
                ];
            }

            for ($month = $visibleStartMonth; $month <= $visibleEndMonth; $month++) {
                $monthName = $this->monthName($month);
                foreach ($branches as $branchCode) {
                    foreach (['current', 'compare'] as $periodKey) {
                        $candidate = $indexed[$branchCode][$periodKey][$metricKey][$month] ?? null;
                        if (
                            is_array($candidate)
                            && trim((string)($candidate['month_name'] ?? '')) !== ''
                        ) {
                            $monthName = trim((string)$candidate['month_name']);
                            break 2;
                        }
                    }
                }

                $row = [
                    'month_id' => $month,
                    'month_name' => $monthName,
                ];

                foreach ($branches as $branchCode) {
                    $currentValue = (float)(
                        $indexed[$branchCode]['current'][$metricKey][$month]['value'] ?? 0
                    );
                    $compareValue = (float)(
                        $indexed[$branchCode]['compare'][$metricKey][$month]['value'] ?? 0
                    );

                    $row[$branchCode . '_current'] = $currentValue;
                    $row[$branchCode . '_compare'] = $compareValue;
                    $totals[$branchCode]['current'] += $currentValue;
                    $totals[$branchCode]['compare'] += $compareValue;
                }

                $rows[] = $row;
            }

            foreach ($branches as $branchCode) {
                $totals[$branchCode]['movement'] = $this->percentChange(
                    (float)$totals[$branchCode]['current'],
                    (float)$totals[$branchCode]['compare']
                );
                $series[] = [
                    'label' => (string)($branchDefinitions[$branchCode]['name'] ?? strtoupper($branchCode))
                        . ' · ' . (string)$periods['current_period_label'],
                    'values' => array_map(
                        static function (array $row) use ($branchCode): float {
                            return (float)$row[$branchCode . '_current'];
                        },
                        $rows
                    ),
                ];
                $series[] = [
                    'label' => (string)($branchDefinitions[$branchCode]['name'] ?? strtoupper($branchCode))
                        . ' · ' . (string)$periods['compare_period_label'],
                    'values' => array_map(
                        static function (array $row) use ($branchCode): float {
                            return (float)$row[$branchCode . '_compare'];
                        },
                        $rows
                    ),
                ];
            }

            $currentAllBranches = array_sum(array_map(
                static function (array $row): float {
                    return (float)$row['current'];
                },
                $totals
            ));
            $compareAllBranches = array_sum(array_map(
                static function (array $row): float {
                    return (float)$row['compare'];
                },
                $totals
            ));

            $leaderCode = null;
            $leaderValue = -1.0;
            foreach ($totals as $branchCode => $branchTotal) {
                if ((float)$branchTotal['current'] > $leaderValue) {
                    $leaderCode = $branchCode;
                    $leaderValue = (float)$branchTotal['current'];
                }
            }

            $metrics[$metricKey] = [
                'key' => $metricKey,
                'label_en' => $metricLabels['en'],
                'label_ar' => $metricLabels['ar'],
                'short_en' => $metricLabels['short_en'],
                'short_ar' => $metricLabels['short_ar'],
                'rows' => $rows,
                'totals' => $totals,
                'current_total' => $currentAllBranches,
                'compare_total' => $compareAllBranches,
                'movement' => $this->percentChange(
                    $currentAllBranches,
                    $compareAllBranches
                ),
                'leader_code' => $leaderCode,
                'leader_value' => max(0.0, $leaderValue),
                'chart_payload' => [
                    'title' => current_lang() === 'ar'
                        ? $metricLabels['ar']
                        : $metricLabels['en'],
                    'labels' => array_map(
                        static function (array $row): string {
                            return (string)$row['month_name'];
                        },
                        $rows
                    ),
                    'series' => $series,
                    'showHeader' => false,
                    'showLegend' => true,
                    'showDataLabels' => false,
                    'stableHover' => true,
                ],
            ];
        }

        $serviceFirstAnalysis = $this->buildServiceFirstAnalysis(
            $branches,
            $branchDefinitions,
            $metrics
        );

        return [
            'ready' => true,
            'branches' => $branchDefinitions,
            'errors' => [],
            'metrics' => $metrics,
            'branch_totals' => [],
            'summary' => [
                'ready_branch_count' => count($branches),
                'metric_count' => count($metrics),
                'service_first_analysis' => $serviceFirstAnalysis,
            ],
            'access_paths' => $repo->accessPaths(),
            'connectivity' => $repo->connectivityDiagnostics(),
            'oracle_writes' => 'NONE',
        ];
    }

    /**
     * Builds a service-first comparison. Every service keeps its own unit and
     * is compared only with the same service in the matched comparison period.
     * Different services are never added together or converted into one score.
     *
     * @param array<int, string> $branches
     * @param array<string, array<string, string>> $branchDefinitions
     * @param array<string, array<string, mixed>> $metrics
     * @return array<string, mixed>
     */
    private function buildServiceFirstAnalysis(
        array $branches,
        array $branchDefinitions,
        array $metrics
    ): array {
        $branchSignals = [];
        foreach ($branches as $branchCode) {
            $branchSignals[$branchCode] = [
                'code' => $branchCode,
                'name' => (string)($branchDefinitions[$branchCode]['name'] ?? strtoupper($branchCode)),
                'improved' => [],
                'declined' => [],
                'stable' => [],
            ];
        }

        $serviceRows = [];
        $prioritySignals = [];
        $positiveSignals = [];
        $latestMonthId = 0;
        $latestMonthName = '';

        foreach ($metrics as $metricKey => $metric) {
            $branchRows = [];
            $states = [];
            $serviceWorstSignal = null;
            $serviceBestSignal = null;

            foreach ($branches as $branchCode) {
                $branchMetric = (array)($metric['totals'][$branchCode] ?? []);
                $movement = isset($branchMetric['movement'])
                    ? (float)$branchMetric['movement']
                    : null;

                $state = 'stable';
                if ($movement !== null && $movement > 0.05) {
                    $state = 'improved';
                } elseif ($movement !== null && $movement < -0.05) {
                    $state = 'declined';
                }

                $focusState = $state === 'improved'
                    ? 'positive'
                    : ($state === 'declined' ? 'negative' : 'stable');
                $focus = $this->domainLeadershipFocus($metricKey, $focusState);

                $signal = [
                    'metric_key' => $metricKey,
                    'metric_label_en' => (string)($metric['label_en'] ?? $metricKey),
                    'metric_label_ar' => (string)($metric['label_ar'] ?? $metricKey),
                    'metric_short_en' => (string)($metric['short_en'] ?? $metricKey),
                    'metric_short_ar' => (string)($metric['short_ar'] ?? $metricKey),
                    'branch_code' => $branchCode,
                    'branch_name' => (string)($branchDefinitions[$branchCode]['name'] ?? strtoupper($branchCode)),
                    'current' => (float)($branchMetric['current'] ?? 0),
                    'compare' => (float)($branchMetric['compare'] ?? 0),
                    'movement' => $movement,
                    'state' => $state,
                    'focus_en' => (string)$focus['en'],
                    'focus_ar' => (string)$focus['ar'],
                ];

                $branchRows[$branchCode] = $signal;
                $states[] = $state;
                $branchSignals[$branchCode][$state][] = $signal;

                if ($state === 'declined') {
                    $prioritySignals[] = $signal;
                    if (
                        $serviceWorstSignal === null
                        || (float)$movement < (float)$serviceWorstSignal['movement']
                    ) {
                        $serviceWorstSignal = $signal;
                    }
                } elseif ($state === 'improved') {
                    $positiveSignals[] = $signal;
                    if (
                        $serviceBestSignal === null
                        || (float)$movement > (float)$serviceBestSignal['movement']
                    ) {
                        $serviceBestSignal = $signal;
                    }
                }
            }

            $monthlyRows = [];
            foreach ((array)($metric['rows'] ?? []) as $monthRow) {
                $monthId = (int)($monthRow['month_id'] ?? 0);
                if ($monthId > $latestMonthId) {
                    $latestMonthId = $monthId;
                    $latestMonthName = (string)($monthRow['month_name'] ?? $monthId);
                }

                $normalizedMonth = [
                    'month_id' => $monthId,
                    'month_name' => (string)($monthRow['month_name'] ?? $monthId),
                    'branches' => [],
                ];

                foreach ($branches as $branchCode) {
                    $current = (float)($monthRow[$branchCode . '_current'] ?? 0);
                    $compare = (float)($monthRow[$branchCode . '_compare'] ?? 0);
                    $movement = $this->percentChange($current, $compare);
                    $state = 'stable';
                    if ($movement !== null && $movement > 0.05) {
                        $state = 'improved';
                    } elseif ($movement !== null && $movement < -0.05) {
                        $state = 'declined';
                    }

                    $normalizedMonth['branches'][$branchCode] = [
                        'current' => $current,
                        'compare' => $compare,
                        'movement' => $movement,
                        'state' => $state,
                    ];
                }

                $monthlyRows[] = $normalizedMonth;
            }

            $comparisonState = 'mixed';
            if ($states !== [] && count(array_unique($states)) === 1) {
                $onlyState = (string)$states[0];
                $comparisonState = $onlyState === 'improved'
                    ? 'both_improved'
                    : ($onlyState === 'declined' ? 'both_declined' : 'both_stable');
            } elseif (
                in_array('improved', $states, true)
                && in_array('declined', $states, true)
            ) {
                $comparisonState = 'different_directions';
            } elseif (in_array('declined', $states, true)) {
                $comparisonState = 'one_declined';
            } elseif (in_array('improved', $states, true)) {
                $comparisonState = 'one_improved';
            }

            $serviceFocusSignal = $serviceWorstSignal ?? $serviceBestSignal;
            $serviceRows[$metricKey] = [
                'key' => $metricKey,
                'label_en' => (string)($metric['label_en'] ?? $metricKey),
                'label_ar' => (string)($metric['label_ar'] ?? $metricKey),
                'short_en' => (string)($metric['short_en'] ?? $metricKey),
                'short_ar' => (string)($metric['short_ar'] ?? $metricKey),
                'comparison_state' => $comparisonState,
                'branches' => $branchRows,
                'focus_en' => (string)($serviceFocusSignal['focus_en'] ?? ''),
                'focus_ar' => (string)($serviceFocusSignal['focus_ar'] ?? ''),
                'monthly_rows' => $monthlyRows,
                'chart_payload' => (array)($metric['chart_payload'] ?? []),
            ];
        }

        foreach ($branches as $branchCode) {
            usort($branchSignals[$branchCode]['improved'], static function (array $a, array $b): int {
                return ((float)($b['movement'] ?? 0)) <=> ((float)($a['movement'] ?? 0));
            });
            usort($branchSignals[$branchCode]['declined'], static function (array $a, array $b): int {
                return ((float)($a['movement'] ?? 0)) <=> ((float)($b['movement'] ?? 0));
            });
        }

        usort($prioritySignals, static function (array $a, array $b): int {
            return ((float)($a['movement'] ?? 0)) <=> ((float)($b['movement'] ?? 0));
        });
        usort($positiveSignals, static function (array $a, array $b): int {
            return ((float)($b['movement'] ?? 0)) <=> ((float)($a['movement'] ?? 0));
        });

        $latestMonth = [
            'month_id' => $latestMonthId,
            'month_name' => $latestMonthName,
            'branches' => [],
        ];

        foreach ($branches as $branchCode) {
            $latestMonth['branches'][$branchCode] = [
                'code' => $branchCode,
                'name' => (string)($branchDefinitions[$branchCode]['name'] ?? strtoupper($branchCode)),
                'improved' => [],
                'declined' => [],
                'stable' => [],
            ];
        }

        if ($latestMonthId > 0) {
            foreach ($serviceRows as $metricKey => $serviceRow) {
                foreach ((array)$serviceRow['monthly_rows'] as $monthRow) {
                    if ((int)($monthRow['month_id'] ?? 0) !== $latestMonthId) {
                        continue;
                    }

                    foreach ($branches as $branchCode) {
                        $branchMonth = (array)($monthRow['branches'][$branchCode] ?? []);
                        $state = (string)($branchMonth['state'] ?? 'stable');
                        $focus = $this->domainLeadershipFocus(
                            $metricKey,
                            $state === 'improved'
                                ? 'positive'
                                : ($state === 'declined' ? 'negative' : 'stable')
                        );

                        $latestSignal = [
                            'metric_key' => $metricKey,
                            'metric_label_en' => (string)$serviceRow['label_en'],
                            'metric_label_ar' => (string)$serviceRow['label_ar'],
                            'branch_code' => $branchCode,
                            'branch_name' => (string)($branchDefinitions[$branchCode]['name'] ?? strtoupper($branchCode)),
                            'current' => (float)($branchMonth['current'] ?? 0),
                            'compare' => (float)($branchMonth['compare'] ?? 0),
                            'movement' => isset($branchMonth['movement'])
                                ? (float)$branchMonth['movement']
                                : null,
                            'state' => $state,
                            'focus_en' => (string)$focus['en'],
                            'focus_ar' => (string)$focus['ar'],
                        ];

                        $latestMonth['branches'][$branchCode][$state][] = $latestSignal;
                    }

                    break;
                }
            }

            foreach ($branches as $branchCode) {
                usort($latestMonth['branches'][$branchCode]['improved'], static function (array $a, array $b): int {
                    return ((float)($b['movement'] ?? 0)) <=> ((float)($a['movement'] ?? 0));
                });
                usort($latestMonth['branches'][$branchCode]['declined'], static function (array $a, array $b): int {
                    return ((float)($a['movement'] ?? 0)) <=> ((float)($b['movement'] ?? 0));
                });
            }
        }

        $defaultServiceKey = '';
        if ($prioritySignals !== []) {
            $defaultServiceKey = (string)$prioritySignals[0]['metric_key'];
        } elseif ($positiveSignals !== []) {
            $defaultServiceKey = (string)$positiveSignals[0]['metric_key'];
        } elseif ($serviceRows !== []) {
            $defaultServiceKey = (string)array_key_first($serviceRows);
        }

        return [
            'branch_signals' => $branchSignals,
            'priority_signals' => $prioritySignals,
            'positive_signals' => $positiveSignals,
            'service_rows' => $serviceRows,
            'latest_month' => $latestMonth,
            'default_service_key' => $defaultServiceKey,
            'mixed_service_totals_used' => false,
            'cross_service_score_used' => false,
        ];
    }

    /**
     * @return array<string, string>
     */
    private function domainLeadershipFocus(string $metricKey, string $statusCode): array
    {
        $base = [
            'outpatient' => [
                'en' => 'Review demand, clinic access and schedule utilization.',
                'ar' => 'مراجعة الطلب وإتاحة العيادات واستغلال الجداول.',
            ],
            'admissions' => [
                'en' => 'Review referral conversion, admission flow and bed availability.',
                'ar' => 'مراجعة تحويل الإحالات وتدفق الدخول وتوافر الأسرة.',
            ],
            'emergency' => [
                'en' => 'Review emergency demand, access pathways and staffing.',
                'ar' => 'مراجعة طلب الطوارئ ومسارات الوصول والتغطية التشغيلية.',
            ],
            'operations' => [
                'en' => 'Review theatre utilization, scheduling and cancellation control.',
                'ar' => 'مراجعة استغلال غرف العمليات والجدولة وضبط الإلغاءات.',
            ],
            'laboratory' => [
                'en' => 'Review ordering demand, clinic activity and laboratory capacity.',
                'ar' => 'مراجعة طلب الفحوصات ونشاط العيادات وطاقة المختبر.',
            ],
            'radiology' => [
                'en' => 'Review imaging demand, scheduling and equipment availability.',
                'ar' => 'مراجعة طلب الأشعة والجدولة وتوافر الأجهزة.',
            ],
        ];

        if ($statusCode === 'positive' || $statusCode === 'improving') {
            return [
                'en' => 'Validate capacity and sustain the improvement.',
                'ar' => 'التحقق من كفاية الطاقة والحفاظ على التحسن.',
            ];
        }

        if ($statusCode === 'stable') {
            return [
                'en' => 'Maintain routine monitoring; no immediate escalation.',
                'ar' => 'الاستمرار في المتابعة الدورية دون تصعيد فوري.',
            ];
        }

        if ($statusCode === 'divergent') {
            return [
                'en' => 'Compare branch operating practices and replicate the stronger model.',
                'ar' => 'مقارنة أساليب التشغيل بين الفرعين وتكرار النموذج الأقوى.',
            ];
        }

        return $base[$metricKey] ?? [
            'en' => 'Review the underlying operational drivers.',
            'ar' => 'مراجعة المحركات التشغيلية الأساسية.',
        ];
    }

    /**
     * @param array<string, mixed> $contract
     * @return array<string, mixed>
     */
    private function resolvePeriods(
        string $reportMode,
        int $year,
        int $compareYear,
        string $fromDate,
        string $toDate,
        array $contract
    ): array {
        if ($reportMode === 'range') {
            $visibleStartMonth = (int)(new DateTimeImmutable($fromDate))->format('n');
            $visibleEndMonth = (int)(new DateTimeImmutable($toDate))->format('n');

            return [
                'current_from' => $fromDate,
                'current_to' => $toDate,
                'compare_from' => $this->shiftDateToYear($fromDate, $compareYear),
                'compare_to' => $this->shiftDateToYear($toDate, $compareYear),
                'current_period_label' => $fromDate . ' - ' . $toDate,
                'compare_period_label' => $this->shiftDateToYear($fromDate, $compareYear)
                    . ' - '
                    . $this->shiftDateToYear($toDate, $compareYear),
                'visible_start_month' => $visibleStartMonth,
                'visible_end_month' => $visibleEndMonth,
                'fair_current_year_comparison' => false,
            ];
        }

        $isFair = !empty($contract['fair_current_year_comparison']);
        $lastCompleteMonth = $isFair
            ? (int)($contract['last_complete_month'] ?? 0)
            : 12;

        if ($lastCompleteMonth < 1) {
            throw new \RuntimeException(
                'No completed month is available for a fair branch comparison.'
            );
        }

        $currentFrom = $isFair
            ? (string)($contract['from_date'] ?? sprintf('%04d-01-01', $year))
            : sprintf('%04d-01-01', $year);
        $currentTo = $isFair
            ? (string)($contract['to_date'] ?? sprintf('%04d-12-31', $year))
            : sprintf('%04d-12-31', $year);

        return [
            'current_from' => $currentFrom,
            'current_to' => $currentTo,
            'compare_from' => $this->shiftDateToYear($currentFrom, $compareYear),
            'compare_to' => $this->shiftDateToYear($currentTo, $compareYear),
            'current_period_label' => $this->monthRangeLabel($year, $lastCompleteMonth),
            'compare_period_label' => $this->monthRangeLabel($compareYear, $lastCompleteMonth),
            'visible_start_month' => 1,
            'visible_end_month' => $lastCompleteMonth,
            'fair_current_year_comparison' => $isFair,
        ];
    }

    private function strictDate(string $value): ?DateTimeImmutable
    {
        $date = DateTimeImmutable::createFromFormat('!Y-m-d', $value);
        $errors = DateTimeImmutable::getLastErrors();

        if (
            !$date instanceof DateTimeImmutable
            || (
                $errors !== false
                && (
                    (int)$errors['warning_count'] > 0
                    || (int)$errors['error_count'] > 0
                )
            )
        ) {
            return null;
        }

        return $date;
    }

    private function shiftDateToYear(string $date, int $year): string
    {
        $source = new DateTimeImmutable($date);
        $month = (int)$source->format('n');
        $day = (int)$source->format('j');
        $lastDay = (int)(new DateTimeImmutable(sprintf('%04d-%02d-01', $year, $month)))
            ->modify('last day of this month')
            ->format('j');

        return sprintf('%04d-%02d-%02d', $year, $month, min($day, $lastDay));
    }

    private function monthRangeLabel(int $year, int $lastMonth): 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[$lastMonth] ?? (string)$lastMonth) . ' ' . $year;
    }

    private function monthName(int $month): string
    {
        $en = [1=>'January',2=>'February',3=>'March',4=>'April',5=>'May',6=>'June',7=>'July',8=>'August',9=>'September',10=>'October',11=>'November',12=>'December'];
        $ar = [1=>'يناير',2=>'فبراير',3=>'مارس',4=>'أبريل',5=>'مايو',6=>'يونيو',7=>'يوليو',8=>'أغسطس',9=>'سبتمبر',10=>'أكتوبر',11=>'نوفمبر',12=>'ديسمبر'];
        $months = current_lang() === 'ar' ? $ar : $en;

        return $months[$month] ?? (string)$month;
    }

    private function percentChange(float $current, float $compare): ?float
    {
        if (abs($compare) < 0.000001) {
            return null;
        }

        return (($current - $compare) / abs($compare)) * 100.0;
    }

    private function safeTechnicalError(string $message): string
    {
        $message = preg_replace('/password=[^;\s]+/i', 'password=[redacted]', $message) ?? $message;
        $message = preg_replace('/\s+/', ' ', $message) ?? $message;

        return trim($message);
    }

    private function oracleErrorCode(string $message): string
    {
        if (preg_match('/ORA-\d{5}/i', $message, $matches) === 1) {
            return strtoupper((string)$matches[0]);
        }

        return 'ORACLE_SOURCE_UNAVAILABLE';
    }
}
