<?php
// phpcs:ignoreFile WordPress.WP.I18n.TextDomainMismatch
class SLN_Func
{
    private static $cachedFilterTimes = array();
    private static $cachedTimes = array();
    private static $cachedTs = array();
    private static $cachedDate;

    private static $cachedTimesFormatted;

    public static function getDays()
    {
        $now = new SLN_DateTime('next Sunday');
        $ret = array();
        for ($i = 1; $i <= 7; $i++) {
            $ret[$i] = self::getDateDayName($now->getTimestamp());
            $now->modify('+1 day');
        }

        return $ret;
    }

    public static function getDateDayName($day)
    {
        return SLN_TimeFunc::translateDate('l', $day );
    }

    public static function countDaysBetweenDatetimes(DateTime $from, DateTime $to)
    {
        $datediff = abs((floor($from->format('U') / (60 * 60 * 24))) - floor($to->format('U') / (60 * 60 * 24)));

        return $datediff > 0 ? $datediff : 1;
    }

    public static function getMonths()
    {
        $now = new \SLN_DateTime("now");
        $ret = array();
        if( setlocale(LC_TIME,0) !== get_locale() ){ setlocale(LC_TIME, get_locale()); }
        for ($i = 1; $i <= 12; $i++) {
            $now->setDate(1970,$i,1);
            if(isset($_POST['sln-tools-export-bookings'])){
                $ret[$i] = date('M',  $now->getTimestamp());
            } else {
                $ret[$i] = SLN_TimeFunc::translateDate('M', $now->getTimestamp());
            }
        }
        return $ret;
    }

    public static function getYears($min = null, $max = null)
    {
        if (!isset($min)) {
            $min = SLN_TimeFunc::date('Y') - 1;
        }
        if (!isset($max)) {
            $max = $min + 2;
        }
        $ret = array();
        for ($i = $min; $i <= $max || count($ret) > 10; $i++) {
            $ret[$i] = $i;
        }

        return $ret;
    }

    public static function filter($val, $filter = null)
    {
        if (empty($filter)) {
            return self::filterUnknownedType($val);
        }
        if ($filter == 'int') {
            return intval($val);
        } elseif ($filter == 'money') {
            return number_format(floatval(str_replace(',', '.', $val)), 2);
        } elseif ($filter == 'float') {
            return floatval(str_replace(',', '.', sanitize_text_field($val)));
        } elseif ($filter == 'time') {
            return SLN_TimeFunc::evalPickedTime($val);
        } elseif ($filter == 'date') {
            if (empty($val)) {
                throw new Exception('Empty date value provided to filter');
            }
            if (is_array($val)) {
                $val = $val['year'] . '-' . $val['month'] . '-' . $val['day'];
            } else {
                $val = SLN_TimeFunc::evalPickedDate($val);
            }
            $ret = (new SLN_DateTime($val))->format('Y-m-d');
            if ($ret == '1970-01-01')
                throw new Exception(sprintf('wrong date %s', esc_html($val)));
            return $ret;
        } elseif ($filter == 'bool') {
            return boolval($val);
        } elseif ($filter == 'set') {
            $ret = array();
            if (!is_array($val)) {
                return $ret;
            }
            foreach ($val as $k => $v) {
                if ($v) {
                    $ret[] = is_numeric($k) || is_bool($k) ? $k : sanitize_text_field($k);
                }
            }

            return $ret;
        } else {
            return self::filterUnknownedType($val);
        }
    }

    public static function filterUnknownedType($val, &$ret=false){

        if(is_array($val)){

            if(!$ret) $ret = array();
            foreach ($val as $k => $v) {
                $ret[$k] =is_array($v) ? self::filterUnknownedType($v,$ret[$k]) :(is_numeric($v) || is_bool($v) || empty($v) ? $v : sanitize_text_field(wp_unslash($v)));
            }

            return $ret;
        }
        return is_numeric($val) || is_bool($val) || empty($val) ? $val : sanitize_textarea_field($val);
    }

    public static function removeAccents($string) {
        return strtolower(trim(preg_replace('~[^0-9a-z]+~i', '-', preg_replace('~&([a-z]{1,2})(acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml);~i', '$1', htmlentities($string, ENT_QUOTES, 'UTF-8'))), ' '));
    }

    static function addUrlParam($url, $k, $v)
    {
        return $url . (strpos($url, '?') === false ? '?' : '&') . http_build_query(array($k => $v));
    }

    static function currPageUrl($useHomeUrl = true)
    {
	if ($useHomeUrl) {
	    $homeUrl  = get_home_url();
	    $pageURL  = sanitize_text_field(implode('/', array_slice(explode('/', $homeUrl), 0, 3)));
	    $pageURL .= sanitize_text_field($_SERVER["REQUEST_URI"]);
	    return $pageURL;
	}

        $pageURL = 'http';
        if ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $_SERVER['SERVER_PORT'] == 443) {
            $pageURL .= "s";
        }
        $pageURL .= "://";
        if ($_SERVER["SERVER_PORT"] != "80" && $_SERVER["SERVER_PORT"] != 443) {
            $pageURL .= sanitize_text_field($_SERVER["SERVER_NAME"]) . ":" . sanitize_text_field($_SERVER["SERVER_PORT"]) . sanitize_text_field($_SERVER["REQUEST_URI"]);
        } else {
            $pageURL .= sanitize_text_field($_SERVER["SERVER_NAME"]) . sanitize_text_field($_SERVER["REQUEST_URI"]);
        }

        return $pageURL;
    }

    public static function getIntervalItemsShort()
    {
        return array(
            '+1 hour' => '1 ' . __('hour', 'salon-booking-system'),
            '+2 hours' => '2 ' . __('hours', 'salon-booking-system'),
            '+3 hours' => '3 ' . __('hours', 'salon-booking-system'),
            '+4 hours' => '4 ' . __('hours', 'salon-booking-system'),
            '+6 hours' => '6 ' . __('hours', 'salon-booking-system'),
            '+12 hours' => '12 ' . __('hours', 'salon-booking-system'),
            '+23 hours' => '23 ' . __('hours', 'salon-booking-system'),
            '+24 hours' => '24 ' . __('hours', 'salon-booking-system'),
            '+48 hours' => '48 ' . __('hours', 'salon-booking-system'),
        );
    }

    public static function getIntervalItems()
    {
        return array(
            '+5 minutes' => __('5 minutes', 'salon-booking-system'),
            '+15 minutes' => __('quarter of an hour', 'salon-booking-system'),
            '+30 minutes' => __('half hour', 'salon-booking-system'),
            '+1 hour' => '1 ' . __('hour', 'salon-booking-system'),
            '+2 hours' => '2 ' . __('hours', 'salon-booking-system'),
            '+3 hours' => '3 ' . __('hours', 'salon-booking-system'),
            '+4 hours' => '4 ' . __('hours', 'salon-booking-system'),
            '+8 hours' => '8 ' . __('hours', 'salon-booking-system'),
            '+16 hours' => '16 ' . __('hours', 'salon-booking-system'),
            '+1 day' => '1 ' . __('day', 'salon-booking-system'),
            '+2 days' => '2 ' . __('days', 'salon-booking-system'),
            '+3 days' => '3 ' . __('days', 'salon-booking-system'),
            '+4 days' => '4 ' . __('days', 'salon-booking-system'),
            '+1 week' => '1 ' . __('week', 'salon-booking-system'),
            '+2 weeks' => '2 ' . __('weeks', 'salon-booking-system'),
            '+3 weeks' => '3 ' . __('weeks', 'salon-booking-system'),
            '+1 month' => '1 ' . __('month', 'salon-booking-system'),
            '+2 months' => '2 ' . __('months', 'salon-booking-system'),
            '+3 months' => '3 ' . __('months', 'salon-booking-system'),
            '+6 months' => '6 ' . __('months', 'salon-booking-system'),
            '+1 year' => '1 ' . __('years', 'salon-booking-system'),
            '+2 years' => '2 ' . __('years', 'salon-booking-system')
        );

        return array(
            '' => 'Always',
            'PT5M' => '5 minutes',
            'PT30M' => 'half hour',
            'PT1H' => '1 hour',
            'PT2H' => '2 hours',
            'PT3H' => '3 hours',
            'PT4H' => '4 hours',
            'PT8H' => '8 hours',
            'PT16H' => '16 hours',
            'P1D' => '1 day',
            'P2D' => '2 days',
            'P3D' => '3 days',
            'P4D' => '4 days',
            'P1W' => '1 week',
            'P2W' => '2 weeks',
            'P3W' => '3 weeks',
            'P1M' => '1 month',
            'P2M' => '2 months',
            'P3M' => '3 months',
            'P6M' => '6 months',
            'P12M' => '1 year',
            'P24M' => '2 years'
        );
    }

    public static function getMinutesIntervals($interval = null, $maxItems = null)
    {
        $start = "00:00";

        $curr = (new SLN_DateTime())->setTime(0,0);
        $interval = isset($interval) ?
            $interval :
            SLN_Plugin::getInstance()->getSettings()->getInterval();
        $maxItems = isset($maxItems) ?
            $maxItems : (24*60/$interval); // as default it is 24 hours
        $items = array();
        do {
            $items[] = $curr->format('H:i');
            $curr = $curr->add(new DateInterval('PT'.((int)$interval).'M'));
            $maxItems--;
        } while ($curr->format("H:i") != $start && $maxItems > 0);
        return $items;
    }

    /**
     * Round a DateTime up to the next booking-interval boundary.
     *
     * @param DateTimeInterface $dateTime
     * @param int               $interval Minutes between slots.
     * @return SLN_DateTime
     */
    public static function alignDateTimeToInterval($dateTime, $interval)
    {
        $interval = max(1, (int) $interval);
        $aligned  = clone $dateTime;
        $totalMins = (int) $aligned->format('H') * 60 + (int) $aligned->format('i');
        $remainder = $totalMins % $interval;
        if ($remainder > 0) {
            $aligned->modify('+' . ($interval - $remainder) . ' minutes');
        }

        return $aligned instanceof SLN_DateTime ? $aligned : new SLN_DateTime($aligned->format('Y-m-d H:i:s'), $aligned->getTimezone());
    }

    /**
     * @param string|DateTimeInterface $time     Time as H:i or DateTime.
     * @param int|null                 $interval Booking interval in minutes; defaults to salon setting.
     * @return string|null Aligned H:i, or null when input is empty.
     */
    public static function alignTimeToInterval($time, $interval = null)
    {
        if ($interval === null) {
            $interval = (int) SLN_Plugin::getInstance()->getSettings()->getInterval();
        }
        $interval = max(1, (int) $interval);

        if ($time instanceof DateTime || $time instanceof DateTimeImmutable) {
            $time = $time->format('H:i');
        }

        $time = self::filter($time, 'time');
        if (empty($time)) {
            return $time;
        }

        return self::alignDateTimeToInterval(new SLN_DateTime('1970-01-01 ' . $time), $interval)->format('H:i');
    }

    /**
     * @param string $time     Time as H:i.
     * @param int|null $interval Booking interval in minutes; defaults to salon setting.
     */
    public static function isTimeAlignedToInterval($time, $interval = null)
    {
        if ($interval === null) {
            $interval = (int) SLN_Plugin::getInstance()->getSettings()->getInterval();
        }
        $interval = max(1, (int) $interval);

        if ($time instanceof DateTime || $time instanceof DateTimeImmutable) {
            $time = $time->format('H:i');
        }

        $time = self::filter($time, 'time');
        if (empty($time)) {
            return true;
        }

        $parts     = explode(':', $time);
        $totalMins = (int) $parts[0] * 60 + (int) $parts[1];

        return ($totalMins % $interval) === 0;
    }

    /**
     * @param $times
     * @param $startDate
     * @param $endDate
     *
     * @return SLN_DateTime[]
     */
    public static function filterTimes($times, DateTimeInterface $startDate, DateTimeInterface $endDate){
        $ret = array();
        $startT = $startDate->format('U');
        $endT = $endDate->format('U');

        $date = $startDate->format('Y-m-d');
        if (self::$cachedDate !== $date) {
            self::$cachedDate  = $date;
            self::$cachedTimes = array();
            self::$cachedTs    = array();
            self::$cachedFilterTimes    = array();
        }

        // cache "self::$cachedFilterTimes" don't depend on $times arg!
        $cachedFilterTimesKey = md5($startDate->format('Y-m-d H:i').'#'.$endDate->format('Y-m-d H:i'));
        if (isset(self::$cachedFilterTimes[$cachedFilterTimesKey])) {
            return self::$cachedFilterTimes[$cachedFilterTimesKey];
        }

        foreach($times as $t){
            $key = $date.' '.$t;
            if (!isset(self::$cachedTimes[$key])) {
                self::$cachedTimes[$key] = new SLN_DateTime($key);
                self::$cachedTs[$key]    = self::$cachedTimes[$key]->format('U');
            }
            $tT = self::$cachedTs[$key];
            if($tT >= $startT && $tT < $endT || $tT == $startT ){
                $ret[] = self::$cachedTimes[$key];
            }
        }

        self::$cachedFilterTimes[$cachedFilterTimesKey] = $ret;

        return $ret;
    }

    public static function getMinutesFromDuration($duration)
    {
        if ($duration instanceof DateTime) {
            $duration = $duration->format('H:i');
        }

        if (is_string($duration) && !empty($duration)) {
            $tmp = explode(':', $duration);
            return (intval($tmp[0]) * 60) + intval($tmp[1]);
        } else {
            return 0;
        }
    } 

    /**
     * Return the smallest standard alignment interval (in minutes) that is >= the given duration.
     *
     * Used by the "Auto-align time slots" feature to determine which time slots to display
     * when a service has a fixed duration. A 70-minute service aligns to 75-minute slots,
     * a 60-minute service aligns to 60-minute slots, etc.
     *
     * Valid intervals cover 30 min → 4 hours in common salon increments.
     * Services < 30 min are excluded by callers; this method still returns 30 as a floor.
     *
     * @param int $durationMinutes Service duration in minutes.
     * @return int Alignment interval in minutes.
     */
    public static function getAutoAlignInterval($durationMinutes)
    {
        $validIntervals = array(30, 45, 60, 75, 90, 105, 120, 135, 150, 180, 210, 240);

        foreach ($validIntervals as $interval) {
            if ($durationMinutes <= $interval) {
                return $interval;
            }
        }

        return 240;
    }

    /**
     * Auto-align time keys when the salon setting is enabled.
     *
     * Splits the slot list into contiguous segments (gap between sorted slots
     * greater than the booking interval). For each segment, uses as anchor the
     * earliest slot in that segment whose minute-of-day is divisible by the
     * alignment interval when such a slot exists; otherwise falls back to the
     * first slot of the segment (previous behaviour). This avoids skewing the
     * whole day from an early grid origin (e.g. 8:30) so that a later real start
     * (e.g. 9:00) is skipped in favour of 9:30.
     *
     * @param array         $times    Keys 'H:i', values preserved.
     * @param string|object $duration Duration as H:i or object with toString().
     * @return array
     */
    public static function filterTimesAlignedToServiceDurationSlots(array $times, $duration)
    {
        if (empty($times)) {
            return $times;
        }

        $durationStr = '';
        if (is_object($duration) && method_exists($duration, 'toString')) {
            $durationStr = $duration->toString();
        } elseif (is_string($duration)) {
            $durationStr = $duration;
        }

        if ($durationStr === '') {
            return $times;
        }

        $durationMinutes = self::getMinutesFromDuration($durationStr);
        if ($durationMinutes < 30) {
            return $times;
        }

        $alignmentInterval = self::getAutoAlignInterval($durationMinutes);
        $bookingInterval     = (int) SLN_Plugin::getInstance()->getSettings()->get('interval');
        if ($bookingInterval < 1) {
            $bookingInterval = 15;
        }

        $keys = array_keys($times);
        usort($keys, 'strcmp');

        $segments = array();
        $current  = array();
        foreach ($keys as $k) {
            if ($current === array()) {
                $current[] = $k;
                continue;
            }
            $pm = self::parseTimeKeyToMinutes(end($current));
            $cm = self::parseTimeKeyToMinutes($k);
            if (null === $pm || null === $cm) {
                $current[] = $k;
                continue;
            }
            if ($cm - $pm > $bookingInterval) {
                $segments[] = $current;
                $current    = array($k);
            } else {
                $current[] = $k;
            }
        }
        if ($current !== array()) {
            $segments[] = $current;
        }

        $alignedTimes = array();
        foreach ($segments as $segmentKeys) {
            if ($segmentKeys === array()) {
                continue;
            }

            $anchorPhase = null;
            foreach ($segmentKeys as $sk) {
                $m = self::parseTimeKeyToMinutes($sk);
                if (null === $m) {
                    continue;
                }
                if ($m % $alignmentInterval === 0) {
                    $anchorPhase = $m;
                    break;
                }
            }

            $firstMin = self::parseTimeKeyToMinutes($segmentKeys[0]);
            if (null === $firstMin) {
                continue;
            }

            $anchorMinutes = (null !== $anchorPhase) ? $anchorPhase : $firstMin;

            foreach ($segmentKeys as $timeKey) {
                $m = self::parseTimeKeyToMinutes($timeKey);
                if (null === $m) {
                    continue;
                }
                if ($m < $anchorMinutes) {
                    continue;
                }
                if (($m - $anchorMinutes) % $alignmentInterval === 0) {
                    $alignedTimes[$timeKey] = $times[$timeKey];
                }
            }
        }

        return empty($alignedTimes) ? $times : $alignedTimes;
    }

    /**
     * @param string $timeKey "H:i"
     * @return int|null Minutes from midnight
     */
    private static function parseTimeKeyToMinutes($timeKey)
    {
        if (! preg_match('/^(\d{2}):(\d{2})$/', $timeKey, $matches)) {
            return null;
        }

        return ((int) $matches[1] * 60) + (int) $matches[2];
    }

    public static function convertToHoursMins($time, $format = '%02d:%02d')
    {
        settype($time, 'integer');
        if ($time < 1) {
            return '00:00';
        }
        $hours = floor($time / 60);
        $minutes = ($time % 60);
        return sprintf($format, $hours, $minutes);
    }

    /**
     * @param string       $k
     * @param string|array $data
     * @return bool
     */
    public static function has($k, $data)
    {
        if (!is_array($data)) {
            return strcmp($k, $data) == 0;
        } else {
            foreach ($data as $p) {
                if (strcmp($k, $p) == 0) {
                    return true;
                }
            }

            return false;
        }
    }

    public static function isSalonPage()
    {
        global $pagenow, $post;
        $ret = false;

        $types = array(SLN_Plugin::POST_TYPE_SERVICE, SLN_Plugin::POST_TYPE_ATTENDANT, SLN_Plugin::POST_TYPE_BOOKING);
        $pt = null; 
        if($pagenow == 'post.php') {
            if ($post) {
                $pt = $post->post_type;
            }
            elseif (isset($_REQUEST['post'])) {
                $pt = get_post_type(intval($_REQUEST['post']));
            }
            elseif (isset($_REQUEST['post_ID'])) {
                $pt = get_post_type(intval($_REQUEST['post_ID']));
            }
        } elseif($pagenow == 'edit.php' || $pagenow == 'post-new.php') {
            $pt = isset($_GET['post_type']) ? sanitize_text_field(wp_unslash($_GET['post_type'])) : null;
        }

        if($pt){
            $ret = strpos($pt, 'sln_') === 0;
        }
        if($pagenow == 'admin.php'){
            $ret = isset($_GET['page']) && strpos(sanitize_text_field(wp_unslash($_GET['page'])), 'salon') === 0;
        }

        $ret = apply_filters('sln.func.isSalonPage', $ret);

        return $ret;
    }


    public static function savePosts($posts){
        $ids  = array();
        foreach ($posts as $label => $post) {
            if (!self::checkPost($post['post']['post_title'], $post['post']['post_type'])) {
                $id = wp_insert_post($post['post']);
                if (isset($post['meta'])) {
                    foreach ($post['meta'] as $k => $v) {
                        update_post_meta($id, $k, $v);
                    }
                }
                $ids[$label] = $id;
            }else{
                $ids[$label] = get_page_by_title($post['post']['post_title'], null, $post['post']['post_type'])->ID;
            }
        }
        return $ids;
    }

    private static function checkPost($title, $post_type)
    {
        return get_page_by_title($title, null, $post_type) ? true : false;
    }

    public static function zerofill($mStretch, $iLength = 2)
    {
    if (!self::$cachedTimesFormatted) {
        self::$cachedTimesFormatted = array(
        0 => '00',
        1 => '01',
        2 => '02',
        3 => '03',
        4 => '04',
        5 => '05',
        6 => '06',
        7 => '07',
        8 => '08',
        9 => '09',
        );
    }

    return $mStretch > 9 ? $mStretch : self::$cachedTimesFormatted[$mStretch];
    }

    public static function get_translated_page_id($page_id, $lang = NULL)
    {
        return isset($page_id) ? SLN_Helper_Multilingual::translateId($page_id, $lang) : NULL;
    }

    public static function updateOption($key, $value, $autoload = null) //need for updating options on all sites of multisite
    {
        if(!is_multisite()) {
            update_option($key, $value, $autoload);
        } else {
            $sites = get_sites();
            foreach ($sites as $site) {
                update_blog_option($site->id, $key, $value);
            }
        }
    }

    public static function deleteOption($key) //need for deleting options on all sites of multisite
    {
        if(!is_multisite()) {
            delete_option($key);
        } else {
            $sites = get_sites();
            foreach ($sites as $site) {
                delete_blog_option($site->id, $key);
            }
        }
    }

    /**
     * Safe wrapper for mb_convert_encoding that handles missing mbstring extension
     * 
     * @param string $string The string to encode
     * @param string $to_encoding Target encoding (default: UTF-8)
     * @param string $from_encoding Source encoding (default: UTF-8)
     * @return string Encoded string or original if mbstring not available
     */
    public static function safe_encoding($string, $to_encoding = 'UTF-8', $from_encoding = 'UTF-8')
    {
        // If mbstring extension is available, use it
        if (function_exists('mb_convert_encoding')) {
            return mb_convert_encoding($string, $to_encoding, $from_encoding);
        }
        
        // Fallback: If converting UTF-8 to UTF-8, just sanitize and return
        if ($to_encoding === 'UTF-8' && $from_encoding === 'UTF-8') {
            if (seems_utf8($string)) {
                return $string;
            }
            // utf8_encode() is deprecated as of PHP 8.2; convert Latin-1 -> UTF-8
            // via iconv when available, otherwise with an exact manual polyfill.
            if (function_exists('iconv')) {
                $converted = @iconv('ISO-8859-1', 'UTF-8//IGNORE', $string);
                if ($converted !== false) {
                    return $converted;
                }
            }
            $out = '';
            $len = strlen($string);
            for ($i = 0; $i < $len; $i++) {
                $o = ord($string[$i]);
                $out .= $o < 128 ? $string[$i] : (chr(0xC0 | ($o >> 6)) . chr(0x80 | ($o & 0x3F)));
            }
            return $out;
        }
        
        // For other conversions, use iconv if available
        if (function_exists('iconv')) {
            return iconv($from_encoding, $to_encoding . '//IGNORE', $string);
        }
        
        // Last resort: return original string
        SLN_Plugin::addLog('WARNING: mbstring extension not available and no fallback worked for encoding conversion');
        return $string;
    }

    /**
     * Safe wrapper for creating DateTimeZone that handles deprecated timezone names
     * 
     * @param string $timezone Timezone identifier (e.g., 'Europe/Kiev', 'America/New_York')
     * @return DateTimeZone Valid DateTimeZone object
     * @throws Exception If timezone is completely invalid
     */
    public static function createDateTimeZone($timezone)
    {
        // Map of deprecated timezone names to their current equivalents
        $deprecated_timezones = array(
            'Europe/Kiev' => 'Europe/Kyiv',        // Renamed in PHP 8.2+
            'Asia/Calcutta' => 'Asia/Kolkata',     // Renamed
            'Asia/Katmandu' => 'Asia/Kathmandu',   // Renamed
            'Asia/Saigon' => 'Asia/Ho_Chi_Minh',   // Renamed
            'Pacific/Ponape' => 'Pacific/Pohnpei', // Renamed
            'Pacific/Truk' => 'Pacific/Chuuk',     // Renamed
        );
        
        // Check if this is a known deprecated timezone
        if (isset($deprecated_timezones[$timezone])) {
            $original_timezone = $timezone;
            $timezone = $deprecated_timezones[$timezone];
            SLN_Plugin::addLog("Timezone '$original_timezone' is deprecated, using '$timezone' instead");
        }
        
        try {
            return new DateTimeZone($timezone);
        } catch (Exception $e) {
            // If timezone is invalid, try to find a close match
            SLN_Plugin::addLog("ERROR: Invalid timezone '$timezone': " . $e->getMessage());
            
            // Try some common fallbacks
            $fallback_attempts = array(
                $timezone,
                str_replace('_', ' ', $timezone), // Try with spaces
                'UTC', // Ultimate fallback
            );
            
            foreach ($fallback_attempts as $attempt) {
                try {
                    SLN_Plugin::addLog("Attempting fallback timezone: $attempt");
                    return new DateTimeZone($attempt);
                } catch (Exception $e2) {
                    continue;
                }
            }
            
            // If all else fails, use WordPress timezone
            SLN_Plugin::addLog("All timezone attempts failed, using WordPress timezone");
            return SLN_TimeFunc::getWpTimezone();
        }
    }
}
