<?php

/**
 * Хелпер для роботи з датами та часом
 * Розширені методи для маніпуляції датами
 *
 * @package Flowaxy\Support\Helpers
 * @version 1.0.0 Alpha prerelease
 */

declare(strict_types=1);

namespace Flowaxy\Support\Helpers;

final class DateHelper
{
    /**
     * Отримати поточну дату та час
     *
     * @return \DateTime
     */
    public static function now(): \DateTime
    {
        return new \DateTime();
    }

    /**
     * Отримати сьогоднішню дату
     *
     * @return \DateTime
     */
    public static function today(): \DateTime
    {
        return new \DateTime('today');
    }

    /**
     * Отримати вчорашню дату
     *
     * @return \DateTime
     */
    public static function yesterday(): \DateTime
    {
        return new \DateTime('yesterday');
    }

    /**
     * Отримати завтрашню дату
     *
     * @return \DateTime
     */
    public static function tomorrow(): \DateTime
    {
        return new \DateTime('tomorrow');
    }

    /**
     * Парсити рядок у дату
     *
     * @param string $time
     * @param \DateTimeZone|null $timezone
     * @return \DateTime|false
     */
    public static function parse(string $time, ?\DateTimeZone $timezone = null): \DateTime|false
    {
        try {
            if ($timezone !== null) {
                return new \DateTime($time, $timezone);
            }

            return new \DateTime($time);
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * Форматувати дату
     *
     * @param \DateTime|string|int $date
     * @param string $format
     * @return string|false
     */
    public static function format(\DateTime|string|int $date, string $format = 'Y-m-d H:i:s'): string|false
    {
        if (is_string($date)) {
            $date = self::parse($date);
            if ($date === false) {
                return false;
            }
        } elseif (is_int($date)) {
            $date = (new \DateTime())->setTimestamp($date);
        }

        return $date->format($format);
    }

    /**
     * Отримати різницю між датами
     *
     * @param \DateTime|string|int $date1
     * @param \DateTime|string|int|null $date2
     * @param bool $absolute
     * @return \DateInterval|false
     */
    public static function diff(\DateTime|string|int $date1, \DateTime|string|int|null $date2 = null, bool $absolute = false): \DateInterval|false
    {
        if (is_string($date1)) {
            $date1 = self::parse($date1);
            if ($date1 === false) {
                return false;
            }
        } elseif (is_int($date1)) {
            $date1 = (new \DateTime())->setTimestamp($date1);
        }

        if ($date2 === null) {
            $date2 = self::now();
        } elseif (is_string($date2)) {
            $date2 = self::parse($date2);
            if ($date2 === false) {
                return false;
            }
        } elseif (is_int($date2)) {
            $date2 = (new \DateTime())->setTimestamp($date2);
        }

        return $date1->diff($date2, $absolute);
    }

    /**
     * Додати інтервал до дати
     *
     * @param \DateTime|string|int $date
     * @param \DateInterval|string $interval
     * @return \DateTime|false
     */
    public static function add(\DateTime|string|int $date, \DateInterval|string $interval): \DateTime|false
    {
        if (is_string($date)) {
            $date = self::parse($date);
            if ($date === false) {
                return false;
            }
        } elseif (is_int($date)) {
            $date = (new \DateTime())->setTimestamp($date);
        }

        if (is_string($interval)) {
            try {
                $interval = new \DateInterval($interval);
            } catch (\Exception $e) {
                return false;
            }
        }

        return $date->add($interval);
    }

    /**
     * Відняти інтервал від дати
     *
     * @param \DateTime|string|int $date
     * @param \DateInterval|string $interval
     * @return \DateTime|false
     */
    public static function sub(\DateTime|string|int $date, \DateInterval|string $interval): \DateTime|false
    {
        if (is_string($date)) {
            $date = self::parse($date);
            if ($date === false) {
                return false;
            }
        } elseif (is_int($date)) {
            $date = (new \DateTime())->setTimestamp($date);
        }

        if (is_string($interval)) {
            try {
                $interval = new \DateInterval($interval);
            } catch (\Exception $e) {
                return false;
            }
        }

        return $date->sub($interval);
    }

    /**
     * Перевірити, чи дата в минулому
     *
     * @param \DateTime|string|int $date
     * @return bool
     */
    public static function isPast(\DateTime|string|int $date): bool
    {
        if (is_string($date)) {
            $date = self::parse($date);
            if ($date === false) {
                return false;
            }
        } elseif (is_int($date)) {
            $date = (new \DateTime())->setTimestamp($date);
        }

        return $date < self::now();
    }

    /**
     * Перевірити, чи дата в майбутньому
     *
     * @param \DateTime|string|int $date
     * @return bool
     */
    public static function isFuture(\DateTime|string|int $date): bool
    {
        if (is_string($date)) {
            $date = self::parse($date);
            if ($date === false) {
                return false;
            }
        } elseif (is_int($date)) {
            $date = (new \DateTime())->setTimestamp($date);
        }

        return $date > self::now();
    }

    /**
     * Перевірити, чи дата сьогодні
     *
     * @param \DateTime|string|int $date
     * @return bool
     */
    public static function isToday(\DateTime|string|int $date): bool
    {
        if (is_string($date)) {
            $date = self::parse($date);
            if ($date === false) {
                return false;
            }
        } elseif (is_int($date)) {
            $date = (new \DateTime())->setTimestamp($date);
        }

        return $date->format('Y-m-d') === self::today()->format('Y-m-d');
    }

    /**
     * Перевірити, чи дата вихідний день
     *
     * @param \DateTime|string|int $date
     * @return bool
     */
    public static function isWeekend(\DateTime|string|int $date): bool
    {
        if (is_string($date)) {
            $date = self::parse($date);
            if ($date === false) {
                return false;
            }
        } elseif (is_int($date)) {
            $date = (new \DateTime())->setTimestamp($date);
        }

        $dayOfWeek = (int)$date->format('w');

        return $dayOfWeek === 0 || $dayOfWeek === 6;
    }

    /**
     * Перевірити, чи дата робочий день
     *
     * @param \DateTime|string|int $date
     * @return bool
     */
    public static function isWeekday(\DateTime|string|int $date): bool
    {
        return !self::isWeekend($date);
    }

    /**
     * Отримати початок дня
     *
     * @param \DateTime|string|int|null $date
     * @return \DateTime|false
     */
    public static function startOfDay(\DateTime|string|int|null $date = null): \DateTime|false
    {
        if ($date === null) {
            $date = self::now();
        } elseif (is_string($date)) {
            $date = self::parse($date);
            if ($date === false) {
                return false;
            }
        } elseif (is_int($date)) {
            $date = (new \DateTime())->setTimestamp($date);
        }

        return $date->setTime(0, 0, 0);
    }

    /**
     * Отримати кінець дня
     *
     * @param \DateTime|string|int|null $date
     * @return \DateTime|false
     */
    public static function endOfDay(\DateTime|string|int|null $date = null): \DateTime|false
    {
        if ($date === null) {
            $date = self::now();
        } elseif (is_string($date)) {
            $date = self::parse($date);
            if ($date === false) {
                return false;
            }
        } elseif (is_int($date)) {
            $date = (new \DateTime())->setTimestamp($date);
        }

        return $date->setTime(23, 59, 59);
    }

    /**
     * Отримати початок тижня
     *
     * @param \DateTime|string|int|null $date
     * @return \DateTime|false
     */
    public static function startOfWeek(\DateTime|string|int|null $date = null): \DateTime|false
    {
        if ($date === null) {
            $date = self::now();
        } elseif (is_string($date)) {
            $date = self::parse($date);
            if ($date === false) {
                return false;
            }
        } elseif (is_int($date)) {
            $date = (new \DateTime())->setTimestamp($date);
        }

        $dayOfWeek = (int)$date->format('w');
        $daysToSubtract = $dayOfWeek === 0 ? 6 : $dayOfWeek - 1;

        return $date->modify("-{$daysToSubtract} days")->setTime(0, 0, 0);
    }

    /**
     * Отримати кінець тижня
     *
     * @param \DateTime|string|int|null $date
     * @return \DateTime|false
     */
    public static function endOfWeek(\DateTime|string|int|null $date = null): \DateTime|false
    {
        $start = self::startOfWeek($date);
        if ($start === false) {
            return false;
        }

        return $start->modify('+6 days')->setTime(23, 59, 59);
    }

    /**
     * Отримати початок місяця
     *
     * @param \DateTime|string|int|null $date
     * @return \DateTime|false
     */
    public static function startOfMonth(\DateTime|string|int|null $date = null): \DateTime|false
    {
        if ($date === null) {
            $date = self::now();
        } elseif (is_string($date)) {
            $date = self::parse($date);
            if ($date === false) {
                return false;
            }
        } elseif (is_int($date)) {
            $date = (new \DateTime())->setTimestamp($date);
        }

        return $date->modify('first day of this month')->setTime(0, 0, 0);
    }

    /**
     * Отримати кінець місяця
     *
     * @param \DateTime|string|int|null $date
     * @return \DateTime|false
     */
    public static function endOfMonth(\DateTime|string|int|null $date = null): \DateTime|false
    {
        if ($date === null) {
            $date = self::now();
        } elseif (is_string($date)) {
            $date = self::parse($date);
            if ($date === false) {
                return false;
            }
        } elseif (is_int($date)) {
            $date = (new \DateTime())->setTimestamp($date);
        }

        return $date->modify('last day of this month')->setTime(23, 59, 59);
    }

    /**
     * Отримати початок року
     *
     * @param \DateTime|string|int|null $date
     * @return \DateTime|false
     */
    public static function startOfYear(\DateTime|string|int|null $date = null): \DateTime|false
    {
        if ($date === null) {
            $date = self::now();
        } elseif (is_string($date)) {
            $date = self::parse($date);
            if ($date === false) {
                return false;
            }
        } elseif (is_int($date)) {
            $date = (new \DateTime())->setTimestamp($date);
        }

        return $date->modify('first day of January')->setTime(0, 0, 0);
    }

    /**
     * Отримати кінець року
     *
     * @param \DateTime|string|int|null $date
     * @return \DateTime|false
     */
    public static function endOfYear(\DateTime|string|int|null $date = null): \DateTime|false
    {
        if ($date === null) {
            $date = self::now();
        } elseif (is_string($date)) {
            $date = self::parse($date);
            if ($date === false) {
                return false;
            }
        } elseif (is_int($date)) {
            $date = (new \DateTime())->setTimestamp($date);
        }

        return $date->modify('last day of December')->setTime(23, 59, 59);
    }

    /**
     * Отримати timestamp
     *
     * @param \DateTime|string|int|null $date
     * @return int|false
     */
    public static function timestamp(\DateTime|string|int|null $date = null): int|false
    {
        if ($date === null) {
            return time();
        }

        if (is_string($date)) {
            $date = self::parse($date);
            if ($date === false) {
                return false;
            }
        } elseif (is_int($date)) {
            return $date;
        }

        return $date->getTimestamp();
    }

    /**
     * Конвертувати в DateTime об'єкт
     *
     * @param \DateTime|string|int $date
     * @return \DateTime|false
     */
    public static function toDateTime(\DateTime|string|int $date): \DateTime|false
    {
        if ($date instanceof \DateTime) {
            return $date;
        }

        if (is_string($date)) {
            return self::parse($date);
        }

        if (is_int($date)) {
            return (new \DateTime())->setTimestamp($date);
        }

        return false;
    }
}
