<?php
namespace SabaiApps\Directories\Component\Calendar\Helper;

use SabaiApps\Directories\Application;
use SabaiApps\Directories\Component\Calendar\CalendarComponent;
use SabaiApps\Directories\Component\Calendar\Model\Occurrence;
use SabaiApps\Directories\Component\Entity\Type\IEntity;
use SabaiApps\Directories\Context;

class CalendarHelper {

	public function scripts( Application $application ) {
		$js = array(
			'var DRTS_Calendar_locale = "' . $application->H( $this->locale( $application ) ) . '";',
			'var DRTS_Calendar_firstDay = ' . ( 7 === ( $start_of_week = $application->getPlatform()->getStartOfWeek() ) ? 0 : $start_of_week ) . ';',
			'var DRTS_Calendar_hourTimeFormat = ' . ( $application->System_Date_is24HourTimeFormat() ? 24 : 12 ) . ';',
		);
		$application->getPlatform()
			->addJsFile( 'luxon.min.js', 'luxon', null, null, true, true )
			->addJsFile( 'fullcalendar.min.js', 'fullcalendar', array( 'luxon' ), null, true, true )
			->addJsFile( 'fullcalendar-luxon.min.js', 'fullcalendar-luxon', array( 'luxon', 'fullcalendar' ), null, true, true )
			->addJsFile( 'calendar-calendar.min.js', 'drts-calendar-calendar', array( 'drts', 'fullcalendar' ), 'directories-pro' )
			->addJsInline( 'drts-calendar-calendar', implode( PHP_EOL, $js ) )
			->addCssFile( 'calendar-calendar.min.css', 'drts-calendar-calendar', 'drts', 'directories-pro' );
	}

	protected function locale( Application $application ) {
		if ( $locale = strtolower( $application->getPlatform()->getLocale() ) ) {
			if ( strpos( $locale, '_' ) ) {
				if ( in_array(
					$locale,
					array(
						'de_at',
						'en_au',
						'en_gb',
						'en_nz',
						'fr_ch',
						'hy_am',
						'pt_br',
						'si_lk',
						'sr_cyrl',
						'ta_in',
						'uz_cy',
						'zh_cn',
						'zh_tw',
					)
				) ) {
					return strtr( $locale, '_', '-' );
				}
				$locale = explode( '_', $locale )[0];
			}
			if ( in_array(
				$locale,
				array(
					'af',
					'ar',
					'az',
					'bg',
					'bn',
					'bs',
					'ca',
					'cs',
					'cy',
					'da',
					'de',
					'el',
					'eo',
					'es',
					'et',
					'eu',
					'fa',
					'fi',
					'fr',
					'gl',
					'he',
					'hi',
					'hr',
					'hu',
					'id',
					'is',
					'it',
					'ja',
					'ka',
					'kk',
					'ko',
					'lb',
					'lt',
					'lv',
					'mk',
					'ms',
					'nb',
					'ne',
					'nl',
					'nn',
					'pl',
					'pt',
					'ro',
					'ru',
					'sk',
					'sl',
					'sq',
					'sr',
					'sv',
					'th',
					'tr',
					'ug',
					'uk',
					'uz',
					'vi',
				)
			) ) {
				return $locale;
			}
		}
		return 'en';
	}

	public function controlsHtml( Application $application, array $options, Context $context ) {
		$options += array(
			'class'     => DRTS_BS_PREFIX . 'mb-2',
			'btn_class' => null,
			'btn_size'  => 'sm',
			'position'  => null,
		);
		if ( ! empty( $options['position'] ) && in_array( $options['position'], array( 'start', 'center', 'end' ), true ) ) {
			$options['class'] .= ' ' . DRTS_BS_PREFIX . 'text-' . $options['position'];
		}
		return sprintf(
			'<div class="%2$s">
    <button type="button" class="drts-calendar-view-control %3$s" data-action="prev"><i class="fas fa-chevron-left"></i></button>        
    <span class="drts-calendar-view-control-current %1$smx-2 %1$sfw-semibold"></span>
    <button type="button" class="drts-calendar-view-control %3$s" data-action="next"><i class="fas fa-chevron-right"></i></button>
</div>',
			DRTS_BS_PREFIX,
			$application->H( $options['class'] ),
			isset( $options['btn_class'] ) ? $application->H( $options['btn_class'] ) : ''
		);
	}

	public function switchHtml( Application $application, array $options, Context $context ) {
		$options += array(
			'class'         => DRTS_BS_PREFIX . 'mb-2',
			'btn_class'     => null,
			'btn_size'      => 'sm',
			'right'         => false,
			'view_settings' => array(),
		);
		if ( ! empty( $options['right'] ) ) {
			$options['class'] .= ' ' . DRTS_BS_PREFIX . 'text-right';
		}
		$html          = array();
		$nav_buttons   = array();
		$nav_buttons[] = sprintf(
			'<button type="button" class="%2$s drts-calendar-view-control" data-action="today">%3$s</button>',
			DRTS_BS_PREFIX,
			$btn_class = isset( $options['btn_class'] ) ? $application->H( $options['btn_class'] ) : '',
			$application->H( __( 'Today', 'directories-pro' ) )
		);
		$nav_buttons[] = sprintf(
			'<button type="button" class="%2$s %1$sdropdown-toggle %1$sdropdown-toggle-split %1$sposition-relative" data-bs-toggle="dropdown" aria-expanded="false"><span class="%1$svisually-hidden">%3$s</span>%4$s</button>',
			DRTS_BS_PREFIX,
			$btn_class,
			$application->H( __( 'Toggle Dropdown', 'directories-pro' ) ),
			$application->View_Nav_notificationDotHtml()
		);
		$nav_buttons[] = '<ul class="' . DRTS_BS_PREFIX . 'dropdown-menu ' . DRTS_BS_PREFIX . 'dropdown-menu-end ' . DRTS_BS_PREFIX . 'small"><li><h6 style="letter-spacing: 0.05em;" class="' . DRTS_BS_PREFIX . 'dropdown-header ' . DRTS_BS_PREFIX . 'fw-bold ' . DRTS_BS_PREFIX . 'text-uppercase">' . $application->H( __( 'Upcoming Dates', 'directories-pro' ) ) . '</h6></li>';
		$nav_buttons[] = sprintf(
			'<li class="drts-calendar-view-jump-item" style="display:none"><a class="%1$sdropdown-item %1$spy-2" href="#" data-action="jump" title="%2$s">
            <div class="drts-calendar-view-jump-item-date %1$sfw-bold %1$stext-dark %1$sfs-6">March 15, 2026</div>
            <div class="drts-calendar-view-jump-item-title %1$ssmall %1$stext-muted">Slot Title</div>
        </a></li>',
			DRTS_BS_PREFIX,
			$application->H( __( 'Jump to next occurrence', 'directories-pro' ) )
		);
		$nav_buttons[] = '</ul>';
		$html[]        = '<div class="drts-calendar-view-switch-nav-buttons ' . DRTS_BS_PREFIX . 'btn-group ' . DRTS_BS_PREFIX . 'me-2">' . implode( PHP_EOL, $nav_buttons ) . '</div>';

		$display_mode_btns     = array();
		$display_modes_enabled = $options['view_settings']['calendar_display_modes'] ?? array( 'month', 'year', 'list' );
		$all_display_modes     = array(
			'month' => 'fas fa-calendar',
			'year'  => 'fas fa-calendar-days',
			'list'  => 'fas fa-list',
		);
		foreach ( $display_modes_enabled as $display_mode ) {
			if ( ! isset( $all_display_modes[ $display_mode ] ) ) {
				continue;
			}

			$display_mode_btns[] = sprintf(
				'<button type="button" class="%3$s%4$s drts-calendar-view-switch" data-action="%1$s"><i class="%2$s"></i></button>',
				$application->H( $display_mode ),
				$application->H( $all_display_modes[ $display_mode ] ),
				$application->H( $options['btn_class'] ),
				isset( $options['view_settings']['calendar_display_mode'] ) && $options['view_settings']['calendar_display_mode'] === $display_mode ? ' ' . DRTS_BS_PREFIX . 'active' : ''
			);
		}
		if ( ! empty( $display_mode_btns ) ) {
			$html[] = sprintf( '<div class="drts-calendar-view-switch-mode-buttons %1$sbtn-group">%2$s</div>', DRTS_BS_PREFIX, implode( PHP_EOL, $display_mode_btns ) );
		}

		return empty( $html ) ? '' : '<div class="' . $application->H( $options['class'] ) . '">' . implode( PHP_EOL, $html ) . '</div>';
	}

	public function isValidOccurrence( Application $application, $occurrenceHash, $status = null ) {
		if ( ( ! $occurrence = $application->getModel( 'Occurrence', 'Calendar' )->hash_is( $occurrenceHash )->fetchOne() )
			|| ( isset( $status ) && $occurrence->status !== $status )
			|| ( ! $entity = $application->Entity_Entity( $occurrence->entity_type, $occurrence->entity_id ) ) // this should not happen
		) {
			return false;
		}

		return $occurrence;
	}

	public function relatedOccurrences( Application $application, Occurrence $occurrence, $num = 5 ) {
		// Sort by start date in ascending order, but if the occurrence is more than 7 days in the future, sort in descending order so that the closest occurrence is shown first.
		if ( $occurrence->start_date - ( 86400 * 7 ) > time() ) {
			$date_from = $occurrence->start_date - ( 86400 * 7 );
			$order     = 'DESC';
		} else {
			$date_from = $occurrence->start_date;
			$order     = 'ASC';
		}
		return $application->getModel( 'Occurrence', 'Calendar' )
			->slotId_is( $occurrence->slot_id )
			->hash_isNot( $occurrence->hash )
			->status_is( CalendarComponent::OCCURRENCE_STATUS_ACTIVE )
			->startDate_isOrGreaterThan( $date_from )
			->fetch( $num, 0, 'start_date', $order );
	}

	public function slotFields( Application $application, $recurringOnly = true ) {
		$fields = array();
		foreach ( $application->getModel( 'FieldConfig', 'Entity' )->type_is( 'calendar_slot' )->fetch()->with( 'Fields' ) as $field_config ) {
			foreach ( $field_config->Fields as $field ) {
				if ( $recurringOnly
					&& empty( $field->getFieldSettings()['enable_recurring'] )
				) {
					continue;
				}

				$fields[ $field_config->name ][] = $field;
			}
		}
		return $fields;
	}

	public function slotOccurrences( Application $application, $fieldName, $slotId, ?IEntity $entity = null, $includeExisting = false ) {
		if ( ! isset( $entity ) ) {
			$entities = $application->Entity_Query( 'post' )->fieldIs( $fieldName, $slotId, 'slot_id' )->fetch( 1 );
			if ( ! $entity = array_shift( $entities ) ) {
				return array();
			}
		}
		if ( ! $slots = $entity->getFieldValue( $fieldName ) ) {
			return array();
		}
		foreach ( $slots as $slot ) {
			if ( isset( $slot['slot_id'] ) && $slot['slot_id'] == $slotId ) {
				$slot = $slot;
				break;
			}
		}
		if ( ! isset( $slot ) ) {
			return array();
		}

		$occurrences = array();
		$tz          = new \DateTimeZone( $application->getPlatform()->getTimeZone() );
		if ( isset( $slot['rrule'] )
			&& strlen( $slot['rrule'] = (string) $slot['rrule'] ) > 0
		) {
			$dtstart = new \DateTime( '@' . ( $slot['start_date'] + $slot['start_time'] ) );
			$dtstart->setTimezone( $tz );
			$rrule_arr = array( 'DTSTART' => $dtstart );
			foreach ( explode( ';', $slot['rrule'] ) as $part ) {
				list($k, $v)     = explode( '=', $part );
				$rrule_arr[ $k ] = $v;
			}
			if ( ! empty( $slot['repeat_end_date'] ) ) {
				if ( $slot['repeat_end_date'] < time() ) {
					return $occurrences;
				}

				$until = new \DateTime( '@' . $slot['repeat_end_date'] );
				$until->setTimezone( $tz );
				$rrule_arr['UNTIL'] = $until;
			}
			$rrule = new \RRule\RRule( $rrule_arr );
			$application->logDebug( sprintf( 'Generating occurrences for slot ID %s with RRULE: %s', $slotId, $rrule->humanReadable() ) );
			$exclude_dates = empty( $slot['exclude_dates'] ) ? array() : explode( ',', $slot['exclude_dates'] );
			if ( $includeExisting
				|| ( ! $last_generated_date = $this->_getLastGeneratedOccurrenceDate( $application, $slotId ) )
			) {
				$from = new \DateTime();
			} else {
				$from = new \DateTime( '@' . ( $last_generated_date + 1 ) );
			}
			$from->setTimezone( $tz );
			$six_months_later = ( new \DateTime() )->modify( '+180 days' );
			$six_months_later->setTimezone( $tz );
			$application->logDebug( sprintf( 'Generating occurrences for slot ID %s from %s to %s', $slotId, $from->format( 'Y-m-d' ), $six_months_later->format( 'Y-m-d' ) ) );
			foreach ( $rrule->getOccurrencesBetween( $from, $six_months_later ) as $occ_date ) {
				$application->logDebug( sprintf( 'Processing occurrence for slot ID %s on %s', $slotId, $occ_date->format( 'Y-m-d' ) ) );
				if ( $occ_date->getTimestamp() < time() ) {
					continue;
				}
				if ( $exclude_dates
					&& in_array( $occ_date->getTimestamp() - $slot['start_time'], $exclude_dates )
				) {
					continue;
				}
				$application->logDebug( sprintf( 'Generating occurrence for slot ID %s on %s', $slotId, $occ_date->format( 'Y-m-d' ) ) );
				$occurrence                      = $this->_toOccurrenceArray(
					$fieldName,
					$slot,
					$entity,
					$occ_date->getTimestamp(),
					$this->_getEntDateTimestamp(
						$occ_date->getTimestamp() - $slot['start_time'],
						$slot['end_time'],
						$slot['start_time']
					)
				);
				$occurrence_hash                 = $this->_getOccurrenceHash( $occurrence );
				$occurrences[ $occurrence_hash ] = $occurrence;
			}
			$occurrence_count = count( $occurrences );
			$application->logDebug( sprintf( 'Generating %d occurrences for slot ID %s', $occurrence_count, $slotId ) );
		} elseif ( $includeExisting
				|| ! $this->_getLastGeneratedOccurrenceDate( $application, $slotId )
			) {

			if ( $slot['start_date'] > time() ) {
				$occurrence                      = $this->_toOccurrenceArray( $fieldName, $slot, $entity );
				$occurrence_hash                 = $this->_getOccurrenceHash( $occurrence );
				$occurrences[ $occurrence_hash ] = $occurrence;
			}
		}
		return $occurrences;
	}

	protected function _toOccurrenceArray( $fieldName, array $slot, IEntity $entity, $startDate = null, $endDate = null ) {
		return array(
			'slot_id'         => $slot['slot_id'],
			'slot_field_name' => $fieldName,
			'start_date'      => $startDate ?? $slot['start_date'] + $slot['start_time'],
			'end_date'        => $endDate ?? $this->_getEntDateTimestamp( $slot['start_date'], $slot['end_time'], $slot['start_time'] ),
			'title'           => $slot['title'],
			'slot_data'       => $slot['data'] ?? array(),
			'entity_id'       => $entity->getId(),
			'entity_type'     => $entity->getType(),
			'bundle_name'     => $entity->getBundleName(),
			'all_day'         => ! empty( $slot['all_day'] ),
		);
	}

	protected function _getEntDateTimestamp( $startDate, $endTime, $startTime ) {
		return $startDate + ( $endTime >= $startTime ? $endTime : 86400 + $endTime );
	}

	protected function _getLastGeneratedOccurrenceDate( Application $application, $slotId ) {
		if ( ! $occurrence = $application->getModel( 'Occurrence', 'Calendar' )
			->slotId_is( $slotId )
			->fetchOne( 'start_date', 'DESC' )
		) {
			return null;
		}

		return $occurrence->start_date;
	}

	protected function _getOccurrenceHash( array $occurrence, $length = 16 ) {
		$hash_data = array(
			'slot_id' => $occurrence['slot_id'],
			'start'   => $occurrence['start_date'],
			'end'     => $occurrence['end_date'],
		);
		return $occurrence['slot_id'] . '_' . substr( hash( 'sha256', json_encode( $hash_data ) ), 0, $length );
	}
}
