<?php
/**
 * Availability Filter Block for WooCommerce Appointments.
 */

defined( 'ABSPATH' ) || exit;

class WC_Appointments_Block_Availability {

	/**
	 * Constructor.
	 */
	public function __construct() {
		add_action( 'init', [ $this, 'register_block' ], 10, 0 );
		// Always attach the product filtering hook; it only acts when GET params are present.
		add_filter( 'loop_shop_post_in', [ $this, 'filter_products_by_availability' ] );

		// Register front-end script used by the filter UI.
		add_action( 'init', [ $this, 'register_assets' ], 9, 0 );
	}

	/**
	 * Register block and server-side renderer.
	 */
	public function register_block(): void {
		if ( function_exists( 'register_block_type' ) ) {
			register_block_type(
			    'woocommerce-appointments/availability-filter',
			    [
					'render_callback' => [ $this, 'render_block' ],
					'editor_script'   => 'wc-appointments-availability-filter-block-editor',
					'editor_style'    => 'wc-appointments-availability-filter-block-editor-style',
				],
			);
		}
	}

	/**
	 * Register and localize assets required by the front-end filter UI.
	 */
	public function register_assets(): void {
		global $wp_locale;

		$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';

		// Editor script for block registration in Gutenberg.
		wp_register_script(
		    'wc-appointments-availability-filter-block-editor',
		    WC_APPOINTMENTS_PLUGIN_URL . '/assets/js/blocks/appointments-block-availability.js',
		    [ 'wp-blocks', 'wp-element', 'wp-i18n', 'wp-block-editor', 'wp-components' ],
		    WC_APPOINTMENTS_VERSION,
		    true,
		);

		// Ensure translations for editor strings.
		if ( function_exists( 'wp_set_script_translations' ) ) {
			wp_set_script_translations(
			    'wc-appointments-availability-filter-block-editor',
			    'woocommerce-appointments',
			    WC_APPOINTMENTS_PLUGIN_PATH . '/languages',
			);
		}

		// Provide locale data directly for JS i18n as a fallback when JSON catalogs are missing.
		$locale = function_exists( 'determine_locale' ) ? determine_locale() : get_locale();
		$wc_appointments_availability_locale_data = [
			'' => [
				'domain' => 'woocommerce-appointments',
				'lang'   => $locale,
			],
			'Filter Products by Availability' => [ __( 'Filter Products by Availability', 'woocommerce-appointments' ) ],
			'Filter products in your store by availability (dates).' => [ __( 'Filter products in your store by availability (dates).', 'woocommerce-appointments' ) ],
			'Start Date:' => [ __( 'Start Date:', 'woocommerce-appointments' ) ],
			'End Date:' => [ __( 'End Date:', 'woocommerce-appointments' ) ],
			'Filter' => [ __( 'Filter', 'woocommerce-appointments' ) ],
		];
		wp_localize_script(
		    'wc-appointments-availability-filter-block-editor',
		    'wcAppointmentsAvailabilityLocaleData',
		    $wc_appointments_availability_locale_data,
		);
		// Editor style for block polish in Gutenberg.
		wp_register_style(
		    'wc-appointments-availability-filter-block-editor-style',
		    WC_APPOINTMENTS_PLUGIN_URL . '/assets/css/blocks/appointments-block-availability-editor.css',
		    [],
		    WC_APPOINTMENTS_VERSION,
		);

		// Ensure dependencies are registered by the plugin init class.
		// The moment/datepicker scripts are registered via WC_Appointments_Init hooks.

		// Availability filter script used on front-end.
		wp_register_script(
		    'wc-appointments-availability-filter',
		    WC_APPOINTMENTS_PLUGIN_URL . '/assets/js/availability-filter' . $suffix . '.js',
		    [ 'jquery-ui-datepicker', 'underscore', 'wc-appointments-moment' ],
		    WC_APPOINTMENTS_VERSION,
		    true,
		);

		// Localize for datepicker.
		wp_localize_script(
		    'wc-appointments-availability-filter',
		    'wc_appointments_availability_filter_params',
		    [
				'closeText'       => esc_js( __( 'Close', 'woocommerce-appointments' ) ),
				'currentText'     => esc_js( __( 'Today', 'woocommerce-appointments' ) ),
				'prevText'        => esc_js( __( 'Previous', 'woocommerce-appointments' ) ),
				'nextText'        => esc_js( __( 'Next', 'woocommerce-appointments' ) ),
				'monthNames'      => array_values( $wp_locale->month ),
				'monthNamesShort' => array_values( $wp_locale->month_abbrev ),
				'dayNames'        => array_values( $wp_locale->weekday ),
				'dayNamesShort'   => array_values( $wp_locale->weekday_abbrev ),
				'dayNamesMin'     => array_values( $wp_locale->weekday_initial ),
				'firstDay'        => get_option( 'start_of_week' ),
			'isRTL'           => is_rtl(),
				'dateFormat'      => wc_appointments_convert_to_moment_format( wc_appointments_date_format() ),
			],
		);
	}

	/**
	 * Server-side render callback.
	 *
	 * Outputs the availability filter form UI and enqueues the required JS.
	 */
	public function render_block( array $attributes = [], $content = '' ) {
		global $wp;

		// Only render on shop or product taxonomy pages.
		if ( ! is_shop() && ! is_product_taxonomy() ) {
			return '';
		}

		// Enqueue the front-end script for datepicker behavior.
		wp_enqueue_script( 'wc-appointments-availability-filter' );

		// Heading: use attribute title if present, otherwise fallback to a default.
		$default_title = __( 'Filter Products by Availability', 'woocommerce-appointments' );
		$title        = $attributes['title'] ?? '';
		$title        = is_string( $title ) ? trim( $title ) : '';
		$heading_html = '<h3 class="wp-block-heading">' . esc_html( $title ?: $default_title ) . '</h3>';

		$min_date = isset( $_GET['min_date'] ) ? wc_clean( wp_unslash( $_GET['min_date'] ) ) : '';
		$max_date = isset( $_GET['max_date'] ) ? wc_clean( wp_unslash( $_GET['max_date'] ) ) : '';

		$min_date_local = $min_date ? date_i18n( wc_appointments_date_format(), strtotime( $min_date ) ) : '';
		$max_date_local = $max_date ? date_i18n( wc_appointments_date_format(), strtotime( $max_date ) ) : '';

		// Compute form action similar to widget implementation.
		if ( '' === get_option( 'permalink_structure' ) ) {
			$form_action = remove_query_arg(
			    [ 'page', 'paged', 'product-page', 'min_date_label', 'max_date_label' ],
			    add_query_arg( $wp->query_string, '', home_url( $wp->request ) ),
			);
		} else {
			$form_action = preg_replace( '%/page/[0-9]+%', '', home_url( trailingslashit( $wp->request ) ) );
		}

		ob_start();
		?>
		<div class="woocommerce wc-appointments-availability-block widget_availability_filter">
			<?php echo $heading_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
			<form method="get" action="<?php echo esc_url( $form_action ); ?>">
				<div class="date_picker_wrapper">
					<div class="date_picker_inner date_picker_start">
						<label for="min_date_label"><?php echo esc_html__( 'Start Date:', 'woocommerce-appointments' ); ?></label>
						<input type="text" id="min_date_label" class="date-picker" value="<?php echo esc_attr( $min_date_local ); ?>" autocomplete="off" readonly="readonly" />
						<input type="hidden" id="min_date" class="date-picker-field" name="min_date" value="<?php echo esc_attr( $min_date ); ?>" autocomplete="off" readonly="readonly" />
					</div>
					<div class="date_picker_inner date_picker_end">
						<label for="max_date_label"><?php echo esc_html__( 'End Date:', 'woocommerce-appointments' ); ?></label>
						<input type="text" id="max_date_label" class="date-picker" value="<?php echo esc_attr( $max_date_local ); ?>" autocomplete="off" readonly="readonly" />
						<input type="hidden" id="max_date" class="date-picker-field" name="max_date" value="<?php echo esc_attr( $max_date ); ?>" autocomplete="off" readonly="readonly" />
					</div>
					<button type="submit" class="button"><?php echo esc_html__( 'Filter', 'woocommerce-appointments' ); ?></button>
					<div class="clear"></div>
				</div>
			</form>
		</div>
		<?php
		return ob_get_clean();
	}

	/**
     * Filter products by availability when GET parameters are present.
     *
     * @return mixed[]|bool|null
     */
    public function filter_products_by_availability() {
		$min_date = isset( $_GET['min_date'] ) ? wc_clean( wp_unslash( $_GET['min_date'] ) ) : '';
		$max_date = isset( $_GET['max_date'] ) ? wc_clean( wp_unslash( $_GET['max_date'] ) ) : '';

		// Only act when both dates are valid.
		if ( $this->is_valid_date( $min_date ) && $this->is_valid_date( $max_date ) ) {
			$maches = $this->get_available_products( $min_date, $max_date );
			if ( $maches ) {
				return $maches;
			}
            return false;
		}
        return null;
	}

	/**
     * Get all available appointment products between dates.
     *
     * @param string $start_date_raw YYYY-MM-DD or YYYY-MM-DD HH:MM
     * @param string $end_date_raw   YYYY-MM-DD or YYYY-MM-DD HH:MM
     * @return mixed[]|null Available post IDs
     */
    protected function get_available_products( $start_date_raw, $end_date_raw ): ?array {
		$start_date = explode( ' ', $start_date_raw );

		if ( ! isset( $start_date[1] ) ) {
			$start_date[1] = '12:00';
		}

		$appointable_product_ids = WC_Data_Store::load( 'product-appointment' )->get_appointable_product_ids( true );
		if ( ! $appointable_product_ids ) {
			return null;
		}

		$maches2 = [];

		foreach ( $appointable_product_ids as $appointable_product_id ) {
			$appointable_product = get_wc_product_appointment( $appointable_product_id );

			$duration_unit = $appointable_product->get_duration_unit(); // Currently unused; kept for future.

			$appointment_form = new WC_Appointment_Form( $appointable_product );
			$check_date       = strtotime( $start_date_raw );
			$end_date_ts      = strtotime( '+ 1 day', strtotime( $end_date_raw ) );
			$min_date_ts      = strtotime( '- 1 day', strtotime( $start_date_raw ) );
			$max_date_ts      = $start_date_raw === $end_date_raw ? strtotime( '+ 2 days', strtotime( $end_date_raw ) ) : strtotime( '+ 1 day', strtotime( $end_date_raw ) );

			$maches    = [];
			$timestamp = $check_date;
			while ( $timestamp < $end_date_ts ) {
				$appointable_day = WC_Product_Appointment_Rule_Manager::check_availability_rules_against_date( $appointable_product, $timestamp );
				if ( $appointable_day ) {
					$maches[] = $timestamp;
				}
				$timestamp = strtotime( '+1 day', $timestamp );
			}

			if ( [] === $maches ) {
				continue;
			}

			$scheduled_day_slots = WC_Appointments_Controller::find_scheduled_day_slots( $appointable_product, $min_date_ts, $max_date_ts, 'Y-m-d' );

			$timestamp2 = $check_date;
			while ( $timestamp2 < $end_date_ts ) {
				$date = date( 'Y-m-d', $timestamp2 );
				if ( ! isset( $scheduled_day_slots['fully_scheduled_days'][ $date ] ) && ! isset( $scheduled_day_slots['unavailable_days'][ $date ] ) ) {
					$maches2[] = $appointable_product->get_id();
				}
				$timestamp2 = strtotime( '+1 day', $timestamp2 );
			}
		}

		return array_unique( $maches2 );
	}

	/**
	 * Validate date input.
	 *
	 * Checks if the provided string is a valid date or date-time string.
	 *
	 * @param string $date Date string to validate.
	 * @return bool        True if valid.
	 */
	protected function is_valid_date( $date ) {
		if (empty( $date )) {
            return false;
        }
        if (10 === strlen( $date )) {
            $d = DateTime::createFromFormat( 'Y-m-d', $date );
            return $d && $d->format( 'Y-m-d' ) === $date;
        }
        if (16 === strlen( $date )) {
            $d = DateTime::createFromFormat( 'Y-m-d H:i', $date );
            return $d && $d->format( 'Y-m-d H:i' ) === $date;
        }
		return false;
	}
}

new WC_Appointments_Block_Availability();