<?php

namespace FluentBooking\App\Services;

class DateTimeHelper
{
    public static function getTimeZones($grouped = false)
    {
        $tz_identifiers = timezone_identifiers_list();

        if (!$grouped) {
            return $tz_identifiers;
        }

        $lists = [];

        $topLevels = [];
        foreach ($tz_identifiers as $tz) {
            $parts = explode('/', $tz);
            if (count($parts) > 1) {
                $lists[$parts[0]][] = $tz;
            } else {
                $topLevels[$tz][] = $tz;
            }
        }

        return array_merge($topLevels, $lists);
    }

    public static function getFlatGroupedTimeZones()
    {
        $tz_identifiers = timezone_identifiers_list();

        $lists = [];
        $topLevels = [];
        foreach ($tz_identifiers as $tz) {
            $parts = explode('/', $tz);
            if (count($parts) > 1) {
                $lists[] = [
                    'label' => $tz,
                    'value' => $tz,
                    'group' => $parts[0]
                ];
            } else {
                $topLevels[] = [
                    'label' => $tz,
                    'value' => $tz,
                    'group' => $tz
                ];
            }
        }

        return $topLevels + $lists;
    }

    public static function getTimeZone()
    {
        $timeZone = wp_timezone_string();
        if (!$timeZone || !in_array($timeZone, \DateTimeZone::listIdentifiers())) {
            $timeZone = 'UTC';
        }

        return $timeZone;
    }

    public static function guessTimeZone()
    {
        $timeZone = self::getTimeZone();

        if (isset($_COOKIE['fluent_booking_user_timezone'])) {
            $timeZone = sanitize_text_field(wp_unslash($_COOKIE['fluent_booking_user_timezone']));
        }

        return $timeZone;
    }

    public static function getValidatedTimeZone($requestedTimeZone)
    {
        static $cached = [];
        if (isset($cached[$requestedTimeZone])) {
            return $cached[$requestedTimeZone];
        }
        
        if (!in_array($requestedTimeZone, \DateTimeZone::listIdentifiers())) {
            $requestedTimeZone = apply_filters('fluent_booking/fallback_timezone', self::getTimeZone(), $requestedTimeZone);
        }

        $cached[$requestedTimeZone] = $requestedTimeZone;
        return $requestedTimeZone;
    }

    public static function convertToUtc($dateTime, $timezone, $format = 'Y-m-d H:i:s')
    {
        $timezone = self::getValidatedTimeZone($timezone);

        $dateTime = new \DateTime($dateTime, new \DateTimeZone($timezone));
        $dateTime->setTimezone(new \DateTimeZone('UTC'));
        return $dateTime->format($format);
    }

    public static function convertFromUtc($dateTime, $timezone, $format = 'Y-m-d H:i:s')
    {
        $dateTime = new \DateTime($dateTime, new \DateTimeZone('UTC'));

        if ($timezone != 'UTC') {
            $timezone = self::getValidatedTimeZone($timezone);
            $dateTime->setTimezone(new \DateTimeZone($timezone));
        }

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

    public static function convertToTimeZone($dateTime, $fromTimeZone, $toTimeZone, $format = 'Y-m-d H:i:s', $date = null)
    {
        if ($fromTimeZone == $toTimeZone) {
            return gmdate($format, strtotime($dateTime)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
        }

        if ($date) {
            $dateTime = gmdate('Y-m-d H:i', strtotime($date . ' ' . gmdate('H:i', strtotime($dateTime)))); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
        }

        $fromTimeZone = self::getValidatedTimeZone($fromTimeZone);
        $toTimeZone = self::getValidatedTimeZone($toTimeZone);

        $dateTime = new \DateTime($dateTime, new \DateTimeZone($fromTimeZone));
        $dateTime->setTimezone(new \DateTimeZone($toTimeZone));
        return $dateTime->format($format);
    }

    public static function getDateWithoutDST($timezone)
    {
        if (!self::isDaylightSavingActive('2024-01-03', $timezone)) {
            return '2024-01-03';
        }
        return '2024-06-03';
    }

    public static function convertToIso($dateTime, $fromTimeZone = 'UTC')
    {
        $fromTimeZone = self::getValidatedTimeZone($fromTimeZone);

        $dateTime = new \DateTime($dateTime, new \DateTimeZone($fromTimeZone));
        return $dateTime->format('Y-m-d\TH:i:s\Z');
    }

    public static function convertFromIso($dateTime, $toTimeZone = 'UTC')
    {
        $toTimeZone = self::getValidatedTimeZone($toTimeZone);
        $dateTime = new \DateTime($dateTime, new \DateTimeZone('UTC'));
        $dateTime->setTimezone(new \DateTimeZone($toTimeZone));
        return $dateTime->format('Y-m-d H:i:s');
    }

    public static function getIsoDurationInMinutes($duration)
    {
        if (!$duration) {
            return 0;
        }

        $duration = new \DateInterval($duration);

        return ($duration->d * 24 * 60) + ($duration->h * 60) + ($duration->i) + ($duration->s / 60);
    }

    public static function getTimestamp($timezone = 'UTC')
    {
        $timezone = self::getValidatedTimeZone($timezone);
        $dateTime = new \DateTime(gmdate('Y-m-d H:i:s'), new \DateTimeZone('UTC')); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
        $dateTime->setTimezone(new \DateTimeZone($timezone));
        $date = $dateTime->format('Y-m-d H:i:s');
        return strtotime($date);
    }

    public static function formatToLocale($dateTime, $for = 'date')
    {
        // $for can be date | date_time | time
        $dateFormat = (string) get_option('date_format', '');

        if ($for == 'date_time') {
            $dateFormat .= ' ' . (string) get_option('time_format', '');
        } elseif ($for == 'time') {
            $dateFormat = (string) get_option('time_format', '');
        }

        return date_i18n($dateFormat, strtotime($dateTime)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
    }

    public static function getFormattedDate($date, $from = '', $to = 'Y-m-d')
    {
        if (!$from) {
            $from = (string) get_option('date_format', '');
        }

        $date = \DateTime::createFromFormat($from, $date);
        return $date ? $date->format($to) : '';
    }

    public static function getAvailableDateFormats()
    {
        $dateFormats = apply_filters('fluent_booking/available_date_formats', [
            'm/d/Y'  => 'm/d/Y - (Ex: 05/20/2024)', // USA
            'd/m/Y'  => 'd/m/Y - (Ex: 20/05/2024)', // Canada, UK
            'd.m.Y'  => 'd.m.Y - (Ex: 20.05.2024)', // Germany
            'n/j/y'  => 'n/j/y - (Ex: 5/20/24)',
            'm/d/y'  => 'm/d/y - (Ex: 05/20/24)',
            'M/d/Y'  => 'M/d/Y - (Ex: May/20/2024)',
            'y/m/d'  => 'y/m/d - (Ex: 24/05/20)',
            'Y-m-d'  => 'Y-m-d - (Ex: 2024-05-20)',
            'd-M-y'  => 'd-M-y - (Ex: 20-May-24)',
            'F j, Y' => 'F j, Y - (Ex: May 20, 2024)'
        ]);

        $formatted = [];
        foreach ($dateFormats as $format => $label) {
            $formatted[] = [
                'label' => $label,
                'value' => $format,
            ];
        }
        return $formatted;
    }

    public static function getFormattedEventTime($eventTime)
    {
        $timeZone = self::guessTimeZone();

        $convertedTime = self::convertToTimeZone($eventTime, 'UTC', $timeZone);

        $eventTime = self::formatToLocale($convertedTime, 'time');

        $eventDate = self::formatToLocale($convertedTime, 'date');

        return $eventTime . ', ' . $eventDate . ' (' . $timeZone . ')';
    }

    public static function convertPhpDateToDayJSFormay($phpFormat)
    {
        // Mapping PHP date format characters to Day.js format characters
        $replacements = [
            // Day
            'd' => 'DD', // Day of the month, 2 digits with leading zeros
            'D' => 'ddd', // A textual representation of a day, three letters
            'j' => 'D',   // Day of the month without leading zeros
            'l' => 'dddd', // A full textual representation of the day of the week
            'N' => 'E',   // ISO-8601 numeric representation of the day of the week
            'S' => 'o',   // English ordinal suffix for the day of the month, 2 characters
            'w' => 'd',   // Numeric representation of the day of the week
            'z' => 'DDD', // The day of the year (starting from 0)

            // Week
            'W' => 'W',   // ISO-8601 week number of year, weeks starting on Monday

            // Month
            'F' => 'MMMM', // A full textual representation of a month
            'm' => 'MM',   // Numeric representation of a month, with leading zeros
            'M' => 'MMM',  // A short textual representation of a month, three letters
            'n' => 'M',    // Numeric representation of a month, without leading zeros
            't' => '',     // Not supported in Day.js (Number of days in the given month)

            // Year
            'L' => '',     // Not supported in Day.js (Whether it's a leap year)
            'o' => 'GGGG', // ISO-8601 week-numbering year
            'Y' => 'YYYY', // A full numeric representation of a year, 4 digits
            'y' => 'YY',   // A two digit representation of a year

            // Time
            'a' => 'a',    // Lowercase Ante meridiem and Post meridiem
            'A' => 'A',    // Uppercase Ante meridiem and Post meridiem
            'B' => '',     // Not supported in Day.js (Swatch Internet time)
            'g' => 'h',    // 12-hour format of an hour without leading zeros
            'G' => 'H',    // 24-hour format of an hour without leading zeros
            'h' => 'hh',   // 12-hour format of an hour with leading zeros
            'H' => 'HH',   // 24-hour format of an hour with leading zeros
            'i' => 'mm',   // Minutes with leading zeros
            's' => 'ss',   // Seconds with leading zeros
            'u' => 'SSS',  // Milliseconds (Day.js uses SSS for fractional seconds)
            'v' => 'SSS',  // Milliseconds (Day.js uses SSS for fractional seconds)

            // Timezone
            'e' => '',     // Not supported in Day.js (Timezone identifier)
            'I' => '',     // Not supported in Day.js (Whether or not the date is in daylight saving time)
            'O' => 'ZZ',   // Difference to Greenwich time (GMT) in hours
            'P' => 'Z',    // Difference to Greenwich time (GMT) with colon between hours and minutes
            'T' => '',     // Not supported in Day.js (Timezone abbreviation)
            'Z' => '',     // Not supported in Day.js (Timezone offset in seconds)

            // Full Date/Time
            'c' => 'YYYY-MM-DDTHH:mm:ssZ', // ISO 8601 date
            'r' => 'ddd, DD MMM YYYY HH:mm:ss ZZ', // RFC 2822 formatted date
            'U' => 'X',   // Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
        ];

        // Replace each PHP date format character with Day.js equivalent
        $dayjsFormat = "";

        for ($i = 0; $i < strlen($phpFormat); $i++) {
            $char = $phpFormat[$i];

            // Special handling for G\hi pattern
            if ($char === 'G' && $i + 2 < strlen($phpFormat) && 
                $phpFormat[$i + 1] === '\\' && $phpFormat[$i + 2] === 'h') {
                $dayjsFormat .= 'H[h]';
                $i += 2;
                continue;
            }

            // Check if the character is escaped
            if ($char === "\\") {
                // Add the next character to the result as is, without mapping
                $i++;
                if ($i < strlen($phpFormat)) {
                    $dayjsFormat .= "\\" . $phpFormat[$i];
                }
                continue;
            }

            // Add the mapped character or the character itself if not found in the mapping
            $dayjsFormat .= $replacements[$char] ?? $char;
        }

        return $dayjsFormat;
    }

    public static function getDateFormatter($isDayJs = false)
    {
        $format = get_option('date_format');
        if ($isDayJs) {
            return self::convertPhpDateToDayJSFormay($format);
        }

        return $format;
    }

    public static function getTimeFormatter($isDayJs = false)
    {
        $format = get_option('time_format');
        if ($isDayJs) {
            return self::convertPhpDateToDayJSFormay($format);
        }

        return $format;
    }

    public static function getTodayDate($timezone = 'UTC', $format = 'Y-m-d')
    {
        $timezone = self::getValidatedTimeZone($timezone);
        $dateTime = new \DateTime('now', new \DateTimeZone($timezone));
        return $dateTime->format($format);
    }

    public static function getDayDifference($dateTime, $fromTimeZone, $toTimeZone, $refDate = 'now')
    {
        $fromTimeZone = self::getValidatedTimeZone($fromTimeZone);
        $toTimeZone = self::getValidatedTimeZone($toTimeZone);

        if ($refDate != 'now') {
            $dateTime = gmdate('Y-m-d H:i', strtotime($refDate . ' ' . gmdate('H:i', strtotime($dateTime)))); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
        }

        $currentDate = new \DateTime($refDate, new \DateTimeZone($fromTimeZone));

        $originalDateTime = new \DateTime($dateTime, new \DateTimeZone($fromTimeZone));

        $originalDateTime->setTimezone(new \DateTimeZone($toTimeZone));

        return $originalDateTime->format('z') - $currentDate->format('z');
    }

    public static function getDaylightSavingTime($timezone)
    {
        $timezone = self::getValidatedTimeZone($timezone);
        $timezone = new \DateTimeZone($timezone);
        $dateTime = new \DateTime('2024-01-01', $timezone);

        $currentOffset = $timezone->getOffset($dateTime);

        $sixMonthsAgo = clone $dateTime;
        $sixMonthsAgo->modify('+6 month');
        $offsetBefore = $timezone->getOffset($sixMonthsAgo);

        $dstDifference = $currentOffset - $offsetBefore;

        return abs($dstDifference) / 60;
    }

    public static function isDaylightSavingActive($dateTime, $timezone)
    {
        if (!$dateTime || !$timezone) {
            return false;
        }

        $timezone = self::getValidatedTimeZone($timezone);

        $dateTimeObject = new \DateTime($dateTime, new \DateTimeZone($timezone));

        $isDstActive = $dateTimeObject->format('I');

        if ($timezone == 'Europe/Dublin') {
            return !$isDstActive;
        }

        return $isDstActive;
    }

    public static function getI18nDateTimeConfig()
    {
        return apply_filters('fluent_booking/i18n_date_time_config', [
            'weekdays'      => array(
                'sunday'    => _x('Sunday', 'calendar day full', 'fluent-booking'),
                'monday'    => _x('Monday', 'calendar day full', 'fluent-booking'),
                'tuesday'   => _x('Tuesday', 'calendar day full', 'fluent-booking'),
                'wednesday' => _x('Wednesday', 'calendar day full', 'fluent-booking'),
                'thursday'  => _x('Thursday', 'calendar day full', 'fluent-booking'),
                'friday'    => _x('Friday', 'calendar day full', 'fluent-booking'),
                'saturday'  => _x('Saturday', 'calendar day full', 'fluent-booking'),
            ),
            'months'        => array(
                'January'   => _x('January', 'calendar month name full', 'fluent-booking'),
                'February'  => _x('February', 'calendar month name full', 'fluent-booking'),
                'March'     => _x('March', 'calendar month name full', 'fluent-booking'),
                'April'     => _x('April', 'calendar month name full', 'fluent-booking'),
                'May'       => _x('May', 'calendar month name full', 'fluent-booking'),
                'June'      => _x('June', 'calendar month name full', 'fluent-booking'),
                'July'      => _x('July', 'calendar month name full', 'fluent-booking'),
                'August'    => _x('August', 'calendar month name full', 'fluent-booking'),
                'September' => _x('September', 'calendar month name full', 'fluent-booking'),
                'October'   => _x('October', 'calendar month name full', 'fluent-booking'),
                'November'  => _x('November', 'calendar month name full', 'fluent-booking'),
                'December'  => _x('December', 'calendar month name full', 'fluent-booking')
            ),
            'weekdaysShort' => array(
                'sun' => _x('Sun', 'calendar day short', 'fluent-booking'),
                'mon' => _x('Mon', 'calendar day short', 'fluent-booking'),
                'tue' => _x('Tue', 'calendar day short', 'fluent-booking'),
                'wed' => _x('Wed', 'calendar day short', 'fluent-booking'),
                'thu' => _x('Thu', 'calendar day short', 'fluent-booking'),
                'fri' => _x('Fri', 'calendar day short', 'fluent-booking'),
                'sat' => _x('Sat', 'calendar day short', 'fluent-booking')
            ),
            'monthsShort'   => array(
                'jan' => _x('Jan', 'calendar month name short', 'fluent-booking'),
                'feb' => _x('Feb', 'calendar month name short', 'fluent-booking'),
                'mar' => _x('Mar', 'calendar month name short', 'fluent-booking'),
                'apr' => _x('Apr', 'calendar month name short', 'fluent-booking'),
                'may' => _x('May', 'calendar month name short', 'fluent-booking'),
                'jun' => _x('Jun', 'calendar month name short', 'fluent-booking'),
                'jul' => _x('Jul', 'calendar month name short', 'fluent-booking'),
                'aug' => _x('Aug', 'calendar month name short', 'fluent-booking'),
                'sep' => _x('Sep', 'calendar month name short', 'fluent-booking'),
                'oct' => _x('Oct', 'calendar month name short', 'fluent-booking'),
                'nov' => _x('Nov', 'calendar month name short', 'fluent-booking'),
                'dec' => _x('Dec', 'calendar month name short', 'fluent-booking')
            ),
            'numericSystem' => _x('0_1_2_3_4_5_6_7_8_9', 'calendar numeric system - Sequence must need to maintained', 'fluent-booking'),
        ]);
    }
}
