<?php
/**
 * The DrezSync Booking field for Gravity Forms.
 *
 * Phase 2, step 1: register the field so it appears in the form editor and
 * renders a date and time picker on the form. The availability rules (calendar,
 * open days, hours, slot length) and live calendar sync come in the next steps,
 * so the time list is empty for now and nothing is wired to a calendar yet.
 *
 * @package DrezSync
 */

defined( 'ABSPATH' ) || exit;

if ( ! class_exists( 'GF_Field' ) ) {
	return;
}

/**
 * A booking field: a date picker plus an available-times list.
 */
class DrezSync_Booking_Field extends GF_Field {

	/**
	 * Field type.
	 *
	 * @var string
	 */
	public $type = 'drezsync_booking';

	/**
	 * Title shown in the form editor field list.
	 *
	 * @return string
	 */
	public function get_form_editor_field_title() {
		return esc_attr__( 'DrezSync Booking', 'drezsync' );
	}

	/**
	 * Description shown in the editor when the field is selected.
	 *
	 * @return string
	 */
	public function get_form_editor_field_description() {
		return esc_attr__( 'Let visitors pick an available date and time, kept in sync with your Google Calendar.', 'drezsync' );
	}

	/**
	 * Icon for the editor button.
	 *
	 * @return string
	 */
	public function get_form_editor_field_icon() {
		return 'dashicons-calendar-alt';
	}

	/**
	 * The editor button and the group it belongs to.
	 *
	 * @return array
	 */
	public function get_form_editor_button() {
		return array(
			'group' => 'advanced_fields',
			'text'  => $this->get_form_editor_field_title(),
		);
	}

	/**
	 * Which settings appear when editing this field.
	 *
	 * @return array
	 */
	public function get_form_editor_field_settings() {
		return array(
			'label_setting',
			'description_setting',
			'rules_setting',
			'error_message_setting',
			'label_placement_setting',
			'admin_label_setting',
			'css_class_setting',
			'conditional_logic_field_setting',
		);
	}

	/**
	 * Support Gravity Forms conditional logic.
	 *
	 * @return bool
	 */
	public function is_conditional_logic_supported() {
		return true;
	}

	/**
	 * Render the field on the form, and as a preview in the editor.
	 *
	 * @param array        $form  Form object.
	 * @param string|array $value Current value.
	 * @param null|array   $entry Entry object.
	 * @return string
	 */
	public function get_field_input( $form, $value = '', $entry = null ) {
		$id        = (int) $this->id;
		$is_editor = is_admin();

		$s_wrap  = 'display:flex;flex-direction:column;gap:12px;';
		$s_date  = 'max-width:320px;';
		$s_hint  = 'margin:0;font-size:14px;opacity:.65;';
		$color   = ( isset( $this->drezsyncColor ) && preg_match( '/^#[0-9a-fA-F]{6}$/', $this->drezsyncColor ) ) ? $this->drezsyncColor : '#ff4e43';


		return $this->render_slot( $id, $value, $is_editor, $s_wrap, $s_date, $s_hint, $color );
	}

	/**
	 * The calendar a booking field should use, given a source value.
	 *
	 * Returns the field's own fixed calendar ID. Pro attaches to the
	 * drezsync_resolve_calendar filter to instead read the calendar from another
	 * field's answer (for example a Location choice).
	 *
	 * @param object $field        The booking field.
	 * @param string $source_value The value of the source field, when dynamic.
	 * @return string
	 */
	public static function resolve_calendar( $field, $source_value = '' ) {
		$calendar = isset( $field->drezsyncCalendar ) ? trim( $field->drezsyncCalendar ) : '';
		return (string) apply_filters( 'drezsync_resolve_calendar', $calendar, $field, $source_value );
	}

	/**
	 * The source field id when this field reads its calendar dynamically, else 0.
	 *
	 * Free is always 0 (fixed calendar). Pro attaches to the
	 * drezsync_dynamic_source_id filter to return the chosen source field id.
	 *
	 * @return int
	 */
	private function dynamic_source_id() {
		return (int) apply_filters( 'drezsync_dynamic_source_id', 0, $this );
	}


	/**
	 * Render the single-day time-slot picker.
	 *
	 * @param int    $id        Field ID.
	 * @param string $value     Current value.
	 * @param bool   $is_editor Whether this is the editor preview.
	 * @param string $s_wrap    Wrapper style.
	 * @param string $s_date    Date input style.
	 * @param string $s_hint    Hint style.
	 * @param string $color     Accent color.
	 * @return string
	 */
	private function render_slot( $id, $value, $is_editor, $s_wrap, $s_date, $s_hint, $color ) {
		$date_val = '';
		$time_val = '';
		if ( is_string( $value ) && preg_match( '/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})$/', $value, $m ) ) {
			$date_val = $m[1];
			$time_val = $m[2];
		}

		$s_times   = 'display:flex;flex-wrap:wrap;gap:8px;';
		$s_pill    = 'appearance:none;-webkit-appearance:none;border:1px solid #d5d5d5;background:#fff;color:#434343;padding:8px 15px;border-radius:999px;font-size:14px;line-height:1.2;cursor:pointer;';
		$s_pill_on = $s_pill . 'background:' . $color . ';border-color:' . $color . ';color:#fff;';
		$slot_len   = max( 5, (int) ( isset( $this->drezsyncSlot ) ? $this->drezsyncSlot : 30 ) );
		$guard_attr = '';
		$cal_field  = $this->dynamic_source_id();

		ob_start();
		?>
		<div class="ginput_container ginput_container_drezsync_booking">
			<div class="drezsync-booking" data-mode="slot" data-slot="<?php echo esc_attr( $slot_len ); ?>"<?php echo $guard_attr; // phpcs:ignore WordPress.Security.EscapeOutput ?> data-cal-field="<?php echo $cal_field ? esc_attr( $cal_field ) : ''; ?>" data-color="<?php echo esc_attr( $color ); ?>" style="<?php echo esc_attr( $s_wrap ); ?>">
				<input type="hidden" name="input_<?php echo absint( $id ); ?>" class="drezsync-booking__value" value="<?php echo esc_attr( is_string( $value ) ? $value : '' ); ?>" />

				<input type="date" class="drezsync-booking__date" aria-label="<?php esc_attr_e( 'Booking date', 'drezsync' ); ?>" style="<?php echo esc_attr( $s_date ); ?>" value="<?php echo esc_attr( $date_val ); ?>" <?php echo $is_editor ? "disabled='disabled'" : ''; // phpcs:ignore WordPress.Security.EscapeOutput ?> />

				<div class="drezsync-booking__times" role="group" aria-label="<?php esc_attr_e( 'Available times', 'drezsync' ); ?>" style="<?php echo esc_attr( $s_times ); ?>">
					<?php if ( $is_editor ) : ?>
						<button type="button" class="drezsync-booking__time-pill" style="<?php echo esc_attr( $s_pill ); ?>" disabled="disabled">9:00 AM</button>
						<button type="button" class="drezsync-booking__time-pill is-selected" style="<?php echo esc_attr( $s_pill_on ); ?>" disabled="disabled">9:30 AM</button>
						<button type="button" class="drezsync-booking__time-pill" style="<?php echo esc_attr( $s_pill ); ?>" disabled="disabled">10:00 AM</button>
					<?php elseif ( $time_val ) : ?>
						<button type="button" class="drezsync-booking__time-pill" style="<?php echo esc_attr( $s_pill_on ); ?>" data-time="<?php echo esc_attr( $time_val ); ?>"><?php echo esc_html( $time_val ); ?></button>
					<?php else : ?>
						<p class="drezsync-booking__hint" style="<?php echo esc_attr( $s_hint ); ?>"><?php esc_html_e( 'Pick a date to see available times.', 'drezsync' ); ?></p>
					<?php endif; ?>
				</div>
			</div>
		</div>
		<?php
		return ob_get_clean();
	}


	/**
	 * Validate the chosen date and time, and re-check the slot is still open.
	 *
	 * @param string|array $value Submitted value.
	 * @param array        $form  Form object.
	 */
	public function validate( $value, $form ) {
		$value = is_string( $value ) ? trim( $value ) : '';

		if ( $this->isRequired && '' === $value ) {
			$this->failed_validation  = true;
			$empty_msg                = esc_html__( 'Please pick a date and time.', 'drezsync' );
			$this->validation_message = $this->errorMessage ? $this->errorMessage : $empty_msg;
			return;
		}

		if ( '' === $value ) {
			return;
		}

		$source_value = '';
		$calendar = self::resolve_calendar( $this, $source_value );
		$capacity = (int) apply_filters( 'drezsync_slot_capacity', 1, $this );


		if ( ! preg_match( '/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})$/', $value, $m ) ) {
			$this->failed_validation  = true;
			$this->validation_message = esc_html__( 'Please pick a valid date and time.', 'drezsync' );
			return;
		}

		if ( '' === $calendar || ! class_exists( 'DrezSync' ) ) {
			return;
		}

		$rules = array(
			'days'     => isset( $this->drezsyncDays ) ? $this->drezsyncDays : array( 1, 2, 3, 4, 5 ),
			'start'    => isset( $this->drezsyncStart ) ? $this->drezsyncStart : '09:00',
			'end'      => isset( $this->drezsyncEnd ) ? $this->drezsyncEnd : '17:00',
			'slot'     => isset( $this->drezsyncSlot ) ? $this->drezsyncSlot : 30,
			'capacity' => $capacity,
		);

		require_once DREZSYNC_DIR . 'includes/booking/class-availability.php';
		$availability = new DrezSync_Availability( DrezSync::instance()->connection );
		$open         = $availability->get_times( $calendar, $m[1], $rules );

		if ( ! in_array( $m[2], $open, true ) ) {
			$this->failed_validation  = true;
			$this->validation_message = esc_html__( 'That time was just taken. Please choose another.', 'drezsync' );
		}
	}

	/**
	 * Format the stored value for the entry detail view.
	 *
	 * @param string $value    Stored value.
	 * @param string $currency Currency (unused).
	 * @param bool   $use_text Use text (unused).
	 * @param string $format   Output format (unused).
	 * @param string $media    Media (unused).
	 * @return string
	 */
	public function get_value_entry_detail( $value, $currency = '', $use_text = false, $format = 'html', $media = 'screen' ) {
		return $this->format_display( $value );
	}

	/**
	 * Format the stored value for the entry list column.
	 *
	 * @param string $value    Stored value.
	 * @param array  $entry    Entry object.
	 * @param string $field_id Field ID.
	 * @param array  $columns  Columns.
	 * @param array  $form     Form object.
	 * @return string
	 */
	public function get_value_entry_list( $value, $entry, $field_id, $columns, $form ) {
		return $this->format_display( $value );
	}

	/**
	 * Turn "Y-m-d H:i" into a friendly, readable date and time.
	 *
	 * @param string $value Stored value.
	 * @return string
	 */
	private function format_display( $value ) {
		return esc_html( $this->friendly_value( $value ) );
	}

	/**
	 * Turn a stored value into a friendly, readable string, without escaping.
	 *
	 * "Y-m-d H:i" becomes "July 15, 2026 at 9:00 AM"; "Y-m-d/Y-m-d" becomes
	 * "August 25 to August 28, 2026". Used by the entry display and by merge tags
	 * so the value reads well wherever it appears, including event titles.
	 *
	 * @param string $value Stored value.
	 * @return string
	 */
	private function friendly_value( $value ) {
		if ( is_string( $value ) && preg_match( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/', $value ) ) {
			$dt = date_create( $value, wp_timezone() );
			if ( $dt ) {
				return wp_date( 'F j, Y \a\t g:i A', $dt->getTimestamp() );
			}
		}
		if ( is_string( $value ) && preg_match( '#^(\d{4}-\d{2}-\d{2})/(\d{4}-\d{2}-\d{2})$#', $value, $m ) ) {
			$in  = date_create( $m[1], wp_timezone() );
			$out = date_create( $m[2], wp_timezone() );
			if ( $in && $out ) {
				return sprintf(
					/* translators: 1: check-in date, 2: check-out date. */
					__( '%1$s to %2$s', 'drezsync' ),
					wp_date( 'F j, Y', $in->getTimestamp() ),
					wp_date( 'F j, Y', $out->getTimestamp() )
				);
			}
		}
		return (string) $value;
	}

	/**
	 * Render the value for merge tags so it reads well in titles, emails, etc.
	 *
	 * @param string $value      Display value.
	 * @param string $input_id   Input id.
	 * @param array  $entry      Entry object.
	 * @param array  $form       Form object.
	 * @param string $modifier   Merge tag modifier.
	 * @param string $raw_value  Raw stored value.
	 * @param bool   $url_encode Whether to URL-encode.
	 * @param bool   $esc_html   Whether to escape HTML.
	 * @param string $format     Output format.
	 * @param bool   $nl2br      Whether to convert newlines.
	 * @return string
	 */
	public function get_value_merge_tag( $value, $input_id, $entry, $form, $modifier, $raw_value, $url_encode, $esc_html, $format, $nl2br ) {
		$pretty = $this->friendly_value( is_string( $raw_value ) && '' !== $raw_value ? $raw_value : $value );
		$pretty = $esc_html ? esc_html( $pretty ) : $pretty;
		$pretty = $url_encode ? rawurlencode( $pretty ) : $pretty;
		return $pretty;
	}
}
