<?php

namespace Plainwork\DateTime;

class DateTimeFormatter
{
    private string $dateFormat = 'j M Y'; // 'Y-m-d'
    private string $timeFormat = 'g:ia'; // 'H:i';

    private static array $monthNames = [
        'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
    ];
    private static $monthFullNames = [
        'January', 'February', 'March', 'April', 'May', 'June',
        'July', 'August', 'September', 'October', 'November', 'December'
    ];
    private static $dayOfWeekNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];

    public function setDateFormat(string $v): self
    {
        $this->dateFormat = $v;

        return $this;
    }

    public function getDateFormat(): string
    {
        return $this->dateFormat;
    }

    public function setTimeFormat(string $v): self
    {
        $this->timeFormat = $v;

        return $this;
    }

    public function getTimeFormat(): string
    {
        return $this->timeFormat;
    }

    public function formatFull(\DateTimeInterface $dt): string
    {
        $ret = [];
        $ret[] = $this->formatDateFull($dt);
        $ret[] = $this->formatTime($dt);
        $ret = join(' ', $ret);

        return $ret;
    }

    public function formatDateFull(\DateTimeInterface $dt, ?string $format = null): string
    {
        $dayOfWeek = (int) $dt->format('w');
        $dayOfWeekLabel = static::$dayOfWeekNames[$dayOfWeek];

        return $dayOfWeekLabel . ', ' . $dt->format($format ?? $this->dateFormat);
    }

    public function formatDateFullWithoutYear(\DateTimeInterface $dt): string
    {
        $dateFormat = $this->stripYearFromFormat($this->dateFormat);

        return $this->formatDateFull($dt, $dateFormat);
    }

    public function formatDateWithoutYear(\DateTimeInterface $dt): string
    {
        $dateFormat = $this->stripYearFromFormat($this->dateFormat);

        return $this->formatDate($dt, $dateFormat);
    }

    private function stripYearFromFormat(string $dateFormat): string
    {
        $posY = strpos($dateFormat, 'Y');
        if (false !== $posY) {
            if (0 == $posY) {
                $dateFormat = substr_replace($dateFormat, '', $posY, 2);
            } else {
                $dateFormat = substr_replace($dateFormat, '', $posY - 1, 2);
            }
            $dateFormat = trim($dateFormat, ' ,');
        }

        return $dateFormat;
    }

    public function formatDate(\DateTimeInterface $dt, ?string $format = null): string
    {
        return $dt->format($format ?? $this->dateFormat);
    }

    public function formatDateTime(\DateTimeInterface $dt): string
    {
        return $this->formatDate($dt) . ' ' . $this->formatTime($dt);
    }

    public function formatParamDate(\DateTimeInterface $dt): string
    {
        return $dt->format('Ymd');
    }

    public function formatDay(\DateTimeInterface $dt): string
    {
        return $dt->format('j');
    }

    public function formatTimeInDay(int $seconds, ?string $format = null): string
    {
        $dt = (new \DateTimeImmutable('2026-01-06'))->modify('+ ' . $seconds . ' seconds');

        return $this->formatTime($dt, $format);
    }

    public function formatTime(\DateTimeInterface $dt, ?string $format = null): string
    {
        if (null === $format) {
            $format = $this->timeFormat;
        }

        if ('12short' === $format) {
            $ret = $this->formatTime($dt, 'g:ia');
            $ret = str_replace(':00', '', $ret);
        } elseif ('12xshort' === $format) {
            $ret = $this->formatTime($dt, 'g:ia');
            $ret = str_replace('am', 'a', $ret);
            $ret = str_replace('pm', 'p', $ret);
            $ret = str_replace(':00', '', $ret);
        } elseif ('24short' === $format) {
            $ret = $this->formatTime($dt, 'H:i');
            $ret = preg_replace('/0(\d\:)/', '${1}', $ret);
            $ret = str_replace(':00', '', $ret);
        } else {
            $ret = $dt->format($format);
        }

        return $ret;
    }

    public function formatHour(\DateTimeInterface $dt, ?string $format = null): string
    {
        $ret = $this->formatTime($dt, $format);
        $pos = strpos($ret, ':');
        if (false !== $pos) {
            $ret = substr_replace($ret, '', $pos, 3);
        }

        return $ret;
    }

    public function formatMonth(\DateTimeInterface $dt)
    {
        return static::$monthNames[$dt->format('n') - 1];
    }

    public function formatYear(\DateTimeInterface $dt)
    {
        return $dt->format('Y');
    }

    public function getMonthNames(): array
    {
        return static::$monthNames;
    }

    public function getMonthFullNames(): array
    {
        return static::$monthFullNames;
    }

    public function formatDayOfWeek($wkd): string
    {
        if ($wkd instanceof \DateTimeInterface) {
            $wkd = $wkd->format('w');
        }

        $wkd = $wkd % 7;
        $ret = '__' . static::$dayOfWeekNames[$wkd] . '__';

        return $ret;
    }

    public function formatDurationAsHours(int $seconds): string
    {
        static $cache = [];
        if (isset($cache[$seconds])) {
            return $cache[$seconds];
        }

        $hours = floor($seconds / (60 * 60));
        $remain = $seconds - $hours * (60 * 60);
        $minutes = floor($remain / 60);

        $hoursView = $hours;
        $minutesView = sprintf('%02d', $minutes);

        $ret = $hoursView . ':' . $minutesView;
        $cache[$seconds] = $ret;

        return $ret;
    }

// maxMeasure can be d, h, m
    public function formatDurationVerbose($seconds, $maxMeasure = 'd', $minMeasure = null)
    {
        static $cache = [];

        $cacheKey = (string) $seconds;
        if (isset($cache[$cacheKey])) {
            return $cache[$cacheKey];
        }

        $measures = ['d' => 'd', 'h' => 'h', 'm' => 'm'];
        if ('d' === $maxMeasure) {
        }
        if ('h' === $maxMeasure) {
            unset($measures['d']);
        }
        if ('m' === $maxMeasure) {
            unset($measures['d']);
            unset($measures['h']);
        }

        if ('d' === $minMeasure) {
            unset($measures['h']);
            unset($measures['m']);
        }
        if ('h' === $minMeasure) {
            unset($measures['m']);
        }
        if ('m' === $minMeasure) {
        }

        $days = isset($measures['d']) ? floor($seconds / (24 * 60 * 60)) : 0;
        $remain = $seconds - $days * (24 * 60 * 60);
        $hours = isset($measures['h']) ? floor($remain / (60 * 60)) : 0;
        $remain = $remain - $hours * (60 * 60);
        $minutes = isset($measures['m']) ? floor($remain / 60) : 0;

        $ret = [];

        if ('d' === $minMeasure) {
            $days = $days + 1;
        }
        if ('h' === $minMeasure) {
            $hours = $hours + 1;
        }

        if ($days) {
            $daysView = $days;
            $daysView = $daysView . ' ' . '__d__';
            $ret[] = $daysView;
        }

        if ($hours) {
            $hoursView = $hours;
            $hoursView = $hoursView . ' ' . '__hour__';
            $ret[] = $hoursView;
        }

        if ($minutes) {
            $minutesView = sprintf('%2d', $minutes);
            $minutesView = $minutesView . ' ' . '__min__';
            $ret[] = $minutesView;
        }

        $ret = join(' ', $ret);

        $cache[$cacheKey] = $ret;

        return $ret;
    }

    public function formatRange(\DateTimeInterface $dt1, \DateTimeInterface $dt2)
    {
        $d1 = $this->formatDateFull($dt1);
        $t1 = $this->formatTime($dt1);
        $d2 = $this->formatDateFull($dt2);
        $t2 = $this->formatTime($dt2);

        if ($d1 === $d2) {
            $ret = $d1 . ' ' . $t1 . ' - ' . $t2;
        } else {
            $ret = $d1 . ' ' . $t1 . ' - ' . $d2 . ' ' . $t2;
        }

        return $ret;
    }

    public function formatDateRange(\DateTimeInterface $date1, \DateTimeInterface $date2)
    {
        $ret = [];

        if ((null === $date1) && (null === $date2)) {
            $ret = '__All time__';
            return $ret;
        } elseif (!$date2) {
            $ret = $this->formatDateFull($date1) . ' ->';
            return $ret;
        } elseif (!$date1) {
            $ret = '-> ' . $this->formatDateFull($date2);
            return $ret;
        } elseif ($date1->format('Y-m-d') === $date2->format('Y-m-d')) {
            $ret = $this->formatDateFull($date1);
            return $ret;
        }

        $skip = [];

        if ($date1 == $date2) {
            return $this->formatDate($date1);
        }

        $year1 = $date1->format('Y');
        $year2 = $date2->format('Y');
        $month1 = $date1->format('n');
        $month2 = $date2->format('n');

    // WHOLE YEAR?
        if (
            ($date1 == $date1->setDate($year1, 1, 1))
            && ($date2 == $date2->setDate($year2, 12, 31))
        ) {
            $ret = ($year1 == $year2) ? $year1 : $year1 . ' - ' . $year2;

            return $ret;
        }

    // WHOLE MONTH?
        if (1 == $date1->format('j')) {
            $test = $date2->modify('+1 day');
            $testMonth = $test->format('n');
            if ($testMonth != $month2) {
                $monthNames = $this->getMonthNames();
                if (($month1 == $month2) && ($year1 == $year2)) {
                    $ret = $monthNames[$month1-1] . ' ' . $year1;
                } elseif ($year1 == $year2) {
                    $ret = $monthNames[$month1-1] . ' - ' . $monthNames[$month2-1] . ' ' . $year1;
                } else {
                    $ret = $monthNames[$month1-1] . ' ' . $year1 . ' - ' . $monthNames[$month2-1] . ' ' . $year2;
                }
                return $ret;
            }
        }

        $dateFormat = $this->dateFormat;
        $dateFormat1 = $dateFormat;
        $dateFormat2 = $dateFormat;

    // skip month in first one
        if (($year2 == $year1) && ($month2 == $month1)) {
            $tags = ['m', 'n', 'M'];
            foreach ($tags as $tag) {
                $posM = strpos($dateFormat1, $tag);
                if ($posM !== false) {
                    break;
                }
            }

            if (false !== $posM) {
                if (0 == $posM) {
                    $dateFormat1 = substr_replace($dateFormat1, '', $posM, 2);
                } else {
                    $dateFormat1 = substr_replace($dateFormat1, '', $posM - 1, 2);
                }
            }
        }

    // skip year in first one
        if ($year2 == $year1) {
            $posY = strpos($dateFormat1, 'Y');
            if (false !== $posY) {
                if (0 == $posY) {
                    $dateFormat1 = substr_replace($dateFormat1, '', $posY, 2);
                } else {
                    $dateFormat1 = substr_replace($dateFormat1, '', $posY - 1, 2);
                }
            }
        }

    // skip trailing ,
        if (',' == substr($dateFormat1, -1)) {
            $dateFormat1 = substr($dateFormat1, 0, -1);
            $dateFormat1 = trim($dateFormat1);
        }

        $viewDate1 = $this->formatDate($date1, $dateFormat1);
        $viewDate2 = $this->formatDate($date2, $dateFormat2);

        $ret = $viewDate1 . ' - ' . $viewDate2;

        return $ret;
    }

    public function timeAgo(\DateTimeInterface $dt, ?\DateTimeInterface $now = null): string
    {
        if (null === $now){
            $now = new \DateTimeImmutable();
        }

        // Get the difference
        $diff = $now->diff($dt);

        // Define time rules from largest to smallest
        $intervals = [
            'y' => 'year',
            'm' => 'month',
            'd' => 'day',
            'h' => 'hour',
            'i' => 'minute',
            's' => 'second',
        ];

        foreach ($intervals as $key => $label) {
            if ($diff->{$key} > 0) {
                $value = $diff->{$key};
                // Handle weeks separately if you want (since diff doesn't have a 'w' property)
                if ($key === 'd' && $value >= 7) {
                    $weeks = floor($value / 7);
                    return $weeks . ' week' . ($weeks > 1 ? 's' : '') . ' ago';
                }
                return $value . ' ' . $label . ($value > 1 ? 's' : '') . ' ago';
            }
        }

        return '__Just now__';
    }

    public function formatPeriod(string $period)
    {
        $qty = substr($period, 0, -1);
        $range = substr($period, -1);

        $label = ['m' => '__Month__', 'w' => '__Week__', 'd' => '__Day__'];
        $ret = $qty . ' ' . $label[$range];

        return $ret;
    }

}
