<?php

namespace App\Representation\Service;

/**
 * Gregorian ↔ Jalali (Solar Hijri) conversion.
 */
class JalaliDateService
{
    public function toJalali(\DateTimeInterface $date): array
    {
        [$gy, $gm, $gd] = [(int)$date->format('Y'), (int)$date->format('m'), (int)$date->format('d')];
        return $this->gregorianToJalali($gy, $gm, $gd);
    }

    /** Returns [year, month, day] in Jalali */
    public function gregorianToJalali(int $gy, int $gm, int $gd): array
    {
        $g_d_no = 365 * $gy + (int)(($gy + 3) / 4) - (int)(($gy + 99) / 100) + (int)(($gy + 399) / 400);
        $g_days = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
        for ($i = 1; $i < $gm; $i++) $g_d_no += $g_days[$i];
        if ($gm > 2 && (($gy % 4 === 0 && $gy % 100 !== 0) || ($gy % 400 === 0))) $g_d_no++;

        $j_d_no = $g_d_no - 79;
        $j_np   = (int)($j_d_no / 12053);
        $j_d_no %= 12053;
        $jy     = 979 + 33 * $j_np + 4 * (int)($j_d_no / 1461);
        $j_d_no %= 1461;

        if ($j_d_no >= 366) {
            $jy     += (int)(($j_d_no - 1) / 365);
            $j_d_no  = ($j_d_no - 1) % 365;
        }

        $j_days = [0, 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29];
        $jm = 0;
        for ($i = 1; $i <= 12; $i++) {
            if ($j_d_no < $j_days[$i]) { $jm = $i; break; }
            $j_d_no -= $j_days[$i];
        }
        $jd = $j_d_no + 1;

        return [$jy, $jm, $jd];
    }

    /** تبدیل timestamp به رشته‌ی شمسی: «۱۴۰۵/۰۴/۰۲ ۰۹:۰۰» (پیش‌فرض با ساعت). */
    public function formatDateTime(int $timestamp, bool $withTime = true): string
    {
        $pattern = $withTime ? 'yyyy/MM/dd HH:mm' : 'yyyy/MM/dd';
        $f = new \IntlDateFormatter(
            'fa_IR@calendar=persian',
            \IntlDateFormatter::NONE,
            \IntlDateFormatter::NONE,
            'Asia/Tehran',
            \IntlDateFormatter::TRADITIONAL,
            $pattern,
        );
        return $f->format($timestamp);
    }

    public function jalaliYear(int $timestamp): int
    {
        return $this->gregorianToJalali(
            (int)date('Y', $timestamp),
            (int)date('m', $timestamp),
            (int)date('d', $timestamp)
        )[0];
    }

    public function jalaliMonth(int $timestamp): int
    {
        return $this->gregorianToJalali(
            (int)date('Y', $timestamp),
            (int)date('m', $timestamp),
            (int)date('d', $timestamp)
        )[1];
    }

    /** Returns [startTs, endTs] for a given Jalali month/year */
    public function jalaliMonthRange(int $jYear, int $jMonth): array
    {
        // Convert first day of Jalali month to Gregorian
        $start = $this->jalaliToGregorian($jYear, $jMonth, 1);
        $daysInMonth = $jMonth <= 6 ? 31 : ($jMonth <= 11 ? 30 : 29);
        $end   = $this->jalaliToGregorian($jYear, $jMonth, $daysInMonth);

        $startTs = mktime(0, 0, 0, $start[1], $start[2], $start[0]);
        $endTs   = mktime(23, 59, 59, $end[1], $end[2], $end[0]);

        return [$startTs, $endTs];
    }

    public function jalaliYearRange(int $jYear): array
    {
        $start = $this->jalaliToGregorian($jYear, 1, 1);
        $end   = $this->jalaliToGregorian($jYear, 12, 29);

        return [
            mktime(0, 0, 0, $start[1], $start[2], $start[0]),
            mktime(23, 59, 59, $end[1], $end[2], $end[0]),
        ];
    }

    public function jalaliToGregorian(int $jy, int $jm, int $jd): array
    {
        $jy += 1595;
        $days = -355779 + 365 * $jy + (int)($jy / 33) * 8 + (int)((($jy % 33) + 3) / 4) + $jd;
        $jm_days = [0, 31, 62, 93, 124, 155, 186, 216, 246, 276, 306, 336];
        $days   += $jm_days[$jm - 1];

        $gy  = 400 * (int)($days / 146097);
        $days %= 146097;
        if ($days > 36524) { $gy += 100 * (int)(--$days / 36524); $days %= 36524; if ($days >= 365) $days++; }
        $gy += 4 * (int)($days / 1461);
        $days %= 1461;
        if ($days > 365) { $gy += (int)(($days - 1) / 365); $days = ($days - 1) % 365; }
        $gd = $days + 1;
        $gm_days = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
        $gm = 0;
        for ($i = 1; $i <= 12; $i++) {
            if ($gd <= $gm_days[$i]) { $gm = $i; break; }
            $gd -= $gm_days[$i];
        }

        return [$gy, $gm, $gd];
    }
}
