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

use SabaiApps\Directories\Component\Entity\Model\Bundle;
use SabaiApps\Directories\Component\Field\IField;
use SabaiApps\Directories\Component\Field\Type\AbstractType;
use SabaiApps\Directories\Application;
use SabaiApps\Directories\Component\Calendar\CalendarComponent;
use SabaiApps\Directories\Component\Entity\Type\IEntity;
use SabaiApps\Directories\Component\Field\Type\IColumnable;
use SabaiApps\Directories\Component\Field\Type\TimeType;

class SlotFieldType extends AbstractType implements IColumnable {

	protected function _fieldTypeInfo() {
		return array(
			'label'            => __( 'Calendar Slot', 'directories-pro' ),
			'entity_types'     => array( 'post', 'user' ),
			'default_settings' => array(
				'enable_title'     => true,
				'enable_recurring' => false,
			),
			'icon'             => 'fas fa-calendar-day',
		);
	}

	public function fieldTypeSchema( array $settings = array() ) {
		return array(
			'columns' => array(
				'title'           => array(
					'type'    => Application::COLUMN_VARCHAR,
					'notnull' => true,
					'length'  => 255,
					'default' => '',
				),
				'start_date'      => array(
					'type'     => Application::COLUMN_INTEGER,
					'notnull'  => true,
					'unsigned' => true,
					'length'   => 10,
					'default'  => 0,
				),
				'start_time'      => array(
					'type'     => Application::COLUMN_INTEGER,
					'notnull'  => true,
					'unsigned' => true,
					'length'   => 8,
					'default'  => 0,
				),
				'end_time'        => array(
					'type'     => Application::COLUMN_INTEGER,
					'notnull'  => true,
					'unsigned' => true,
					'length'   => 8,
					'default'  => 0,
				),
				'all_day'         => array(
					'type'     => Application::COLUMN_INTEGER,
					'notnull'  => true,
					'unsigned' => true,
					'length'   => 1,
					'default'  => 0,
				),
				'rrule'           => array(
					'type'    => Application::COLUMN_TEXT,
					'notnull' => false,
					'default' => null,
				),
				'repeat_end_date' => array(
					'type'     => Application::COLUMN_INTEGER,
					'notnull'  => false,
					'unsigned' => true,
					'length'   => 10,
					'default'  => null,
				),
				'exclude_dates'   => array(
					'type'    => Application::COLUMN_TEXT,
					'notnull' => false,
					'default' => null,
				),
				'slot_id'         => array(
					'type'    => Application::COLUMN_VARCHAR,
					'notnull' => true,
					'length'  => 12,
					'default' => '',
				),
				'data'            => array(
					'type'    => Application::COLUMN_JSON,
					'notnull' => false,
					'default' => null,
				),
			),
			'indexes' => array(
				'slot_id'    => array(
					'fields' => array( 'slot_id' => array( 'sorting' => 'ascending' ) ),
					'unique' => true,
				),
				'start_date' => array(
					'fields' => array( 'start_date' => array( 'sorting' => 'ascending' ) ),
				),
				'start_time' => array(
					'fields' => array( 'start_time' => array( 'sorting' => 'ascending' ) ),
				),
				'all_day'    => array(
					'fields' => array( 'all_day' => array( 'sorting' => 'ascending' ) ),
				),
			),
		);
	}

	public function fieldTypeSupports( Bundle $bundle ) {
		return parent::fieldTypeSupports( $bundle ) && empty( $bundle->info['parent'] );
	}

	public function fieldTypeSettingsForm( $fieldType, Bundle $bundle, array $settings, array $parents = array(), array $rootParents = array() ) {
		$form = array(
			'enable_title'     => array(
				'#type'          => 'checkbox',
				'#title'         => __( 'Enable title for each slot', 'directories-pro' ),
				'#default_value' => ! empty( $settings['enable_title'] ),
				'#description'   => __( 'If checked, a title can be specified for each calendar slot.', 'directories-pro' ),
			),
			'enable_recurring' => array(
				'#type'          => 'checkbox',
				'#title'         => __( 'Enable recurring options', 'directories-pro' ),
				'#default_value' => ! empty( $settings['enable_recurring'] ),
				'#description'   => __( 'If checked, recurring options are available when adding a calendar slot.', 'directories-pro' ),
			),
		);
		if ( ! empty( $bundle->info['booking_enable'] ) ) {
			$form['enable_booking'] = array(
				'#type'          => 'checkbox',
				'#title'         => __( 'Enable booking for slots', 'directories-pro' ),
				'#default_value' => ! empty( $settings['enable_booking'] ),
				'#description'   => __( 'If checked, slots can be booked by users.', 'directories-pro' ),
			);
		}

		return $form;
	}

	public function fieldTypeOnSaveValues( IField $field, array $values, ?array $currentValues = null, array &$extraArgs = array() ) {
		$settings         = $field->getFieldSettings();
		$enable_recurring = ! empty( $settings['enable_recurring'] );
		$enable_title     = ! empty( $settings['enable_title'] );
		$ret              = array();
		foreach ( $values as $value ) {
			if ( ! is_array( $value )
				|| empty( intval( $value['start_date'] ?? 0 ) )
			) {
				continue;
			}

			if ( ! empty( $value['all_day'] ) ) {
				$value['start_time'] = 0;
				$value['end_time']   = 86399; // 23:59:59
			} elseif ( ! isset( $value['start_time'] )
				|| ! isset( $value['end_time'] )
			) {
				continue;
			}

			$value = TimeType::normalizeTime( $value, 'start_time', 'end_time' );

			if ( $enable_title ) {
				$value['title'] = isset( $value['title'] ) ? trim( (string) $value['title'] ) : '';
			} else {
				$value['title'] = '';
			}

			if ( ! $enable_recurring
				|| empty( $value['repeat'] )
			) {
				$value['rrule']           = $value['exclude_dates'] = '';
				$value['repeat_end_date'] = 0;
			} else {
				if ( false === $rrule_str = self::getRrule( $value['repeat'], $value ) ) {
					continue;
				}

				$value['rrule'] = $rrule_str;
				if ( ! empty( $value['exclude_dates'] )
					&& is_array( $value['exclude_dates'] )
					&& ( $exclude_dates = array_filter( array_map( 'intval', $value['exclude_dates'] ) ) )
				) {
					$value['exclude_dates'] = implode( ',', $exclude_dates );
				} else {
					$value['exclude_dates'] = '';
				}

				if ( ! empty( $value['repeat_end_date'] ) ) {
					$value['repeat_end_date'] = intval( $value['repeat_end_date'] );
				} else {
					$repeat_end_after = isset( $value['repeat_end_after'] ) ? intval( $value['repeat_end_after'] ) : 10;
					if ( $repeat_end_after < 1 ) {
						$repeat_end_after = 1;
					}
					// Calculate repeat_end_date based on rrule
					$dtstart = new \DateTime( '@' . ( $value['start_date'] - $value['start_time'] ) );
					if ( ! isset( $tz ) ) {
						$tz = new \DateTimeZone( $this->_application->getPlatform()->getTimeZone() );
					}
					$dtstart->setTimezone( $tz );
					try {
						$rrule = new \RRule\RRule( $rrule_str_full = $rrule_str . ';COUNT=' . $repeat_end_after, $dtstart );
					} catch ( \Exception $e ) {
						// Invalid rrule
						$this->_application->logError( 'An error occurred while processing the repeat rule: ' . $rrule_str_full . ' ' . $e->getMessage() );
						continue;
					}
					$occ = iterator_to_array( $rrule );
					if ( empty( $exclude_dates ) ) {
						$last = end( $occ );
					} else {
						// Re-generate occurrences to account for excluded dates
						$occ       = array_values(
							array_filter(
								$occ,
								function ( $dt ) use ( $exclude_dates ) {
									return ! in_array( $dt->getTimestamp(), $exclude_dates, true );
								}
							)
						);
						$last      = end( $occ );
						$occ_count = count( $occ );
						if ( $occ_count < $repeat_end_after ) {
							try {
								$rrule2 = new \RRule\RRule( $rrule_str, $last->setTimezone( $tz ) );
							} catch ( \Exception $e ) {
								$this->_application->logError( 'An error occurred while processing the repeat rule: ' . $rrule_str . ' ' . $e->getMessage() );
								continue;
							}
							while ( $occ_count < $repeat_end_after ) {
								$next = $rrule2->getOccurrencesAfter( $last, false, 1 );
								if ( empty( $next ) ) {
									break;
								}

								$next = $next[0];
								if ( ! in_array( $next->setTime( 0, 0, 0 )->getTimestamp(), $exclude_dates, true ) ) {
									++$occ_count;
								}
								$last = $next;
							}
						}
					}
					$value['repeat_end_date'] = $last->setTime( 23, 59, 59 )->getTimestamp();
				}
			}
			unset( $value['repeat'] );
			if ( ! isset( $value['slot_id'] )
				|| ( ! $value['slot_id'] = trim( (string) $value['slot_id'] ) )
			) {
				$value['slot_id'] = $this->_application->getPlatform()->getRandomId( 12 );
			}
			if ( empty( $value['data'] ) ) {
				unset( $value['data'] );
			}
			$ret[] = $value;
		}
		return $ret;
	}

	public static function getRrule( $repeatType, array $value ) {
		switch ( $repeatType ) {
			case 'daily':
				return 'FREQ=DAILY';
			case 'weekly':
				if ( empty( $value['days'] )
					|| ! is_array( $value['days'] )
					|| ( ! $days = array_filter( array_map( array( __CLASS__, '_getRruleDay' ), $value['days'] ) ) )
				) {
					return false;
				}
				return 'FREQ=WEEKLY;BYDAY=' . implode( ',', $days );
			case 'monthly':
				if ( isset( $value['monthday'] ) ) {
					if ( is_numeric( $value['monthday'] )
						&& ( 0 !== $monthday = intval( $value['monthday'] ) )
						&& $monthday >= -31
						&& $monthday <= 31
					) {
						return 'FREQ=MONTHLY;BYMONTHDAY=' . $monthday;
					} else {
						return false;
					}
				} elseif ( isset( $value['week']['day_pos'], $value['week']['day'] ) ) {
					if ( is_numeric( $value['week']['day_pos'] )
						&& ( $day_pos = intval( $value['week']['day_pos'] ) )
						&& $day_pos >= -1
						&& $day_pos <= 4
						&& is_numeric( $value['week']['day'] )
						&& ( $day = self::_getRruleDay( intval( $value['week']['day'] ) ) )
					) {
						return 'FREQ=MONTHLY;BYDAY=' . $day . ';BYSETPOS=' . $day_pos;
					} else {
						return false;
					}
				} else {
					return false;
				}
			case 'yearly':
				return 'FREQ=YEARLY';
			default:
		}
		return false;
	}

	protected static function _getRruleDay( $value ) {
		switch ( $value ) {
			case 1:
				return 'MO';
			case 2:
				return 'TU';
			case 3:
				return 'WE';
			case 4:
				return 'TH';
			case 5:
				return 'FR';
			case 6:
				return 'SA';
			case 7:
				return 'SU';
			default:
				return null;
		}
	}

	public function fieldTypeIsModified( IField $field, $valueToSave, $currentLoadedValue ) {
		if ( count( $currentLoadedValue ) !== count( $valueToSave ) ) {
			return true;
		}

		foreach ( array_keys( $currentLoadedValue ) as $key ) {
			if ( count( $currentLoadedValue[ $key ] ) !== count( $valueToSave[ $key ] )
				|| array_diff_assoc( $currentLoadedValue[ $key ], $valueToSave[ $key ] )
			) {
				return true;
			}
		}
		return false;
	}

	public function fieldTypeOnRemoved( IField $field ) {
		// Mark all occurrences for the slot as cancelled
		$criteria = $this->_application->getModel( null, 'Calendar' )->createCriteria( 'Occurrence' )
			->slotFieldName_is( $field->getFieldName() );
		$this->_application->getModel( null, 'Calendar' )->getGateway( 'Occurrence' )->updateByCriteria(
			$criteria,
			array( 'occurrence_status' => CalendarComponent::OCCURRENCE_STATUS_CANCELLED )
		);
	}

	public function fieldColumnableInfo( IField $field ) {
		return array(
			'' => array(
				'label' => $field->getFieldLabel(),
			),
		);
	}

	public function fieldColumnableColumn( IField $field, IEntity $entity, $column = '' ) {
		if ( ! $values = $entity->getFieldValue( $field->getFieldName() ) ) {
			return;
		}

		$slot_ids = $dates = array();
		foreach ( array_keys( $values ) as $key ) {
			$slot_ids[] = $values[ $key ]['slot_id'];
		}
		$occurrences_count = $this->_application->getComponent( 'Calendar' )->getModel( 'Occurrence' )
			->slotId_in( $slot_ids )
			->startDate_isOrGreaterThan( time() )
			->status_is( CalendarComponent::OCCURRENCE_STATUS_ACTIVE )
			->count();
		if ( ! $occurrences_count ) {
			return;
		}

		return $this->_application->LinkTo(
			$occurrences_count,
			$this->_application->AdminUrl(
				$this->_application->Entity_BundleTypeInfo_adminPath( $field->Bundle ) . '/calendar/occurrences',
				array(
					'entity_id'       => $entity->getId(),
					'slot_field_name' => $field->getFieldName(),
				),
				'',
				'&'
			),
			array(),
			array()
		);
	}
}
