<?php
/**
 * Plugin Name: Harbor Equipment Rental for WooCommerce
 * Description: Turn any WooCommerce product into a rentable item with date-range booking, per-day pricing, and automatic availability blocking.
 * Version:     1.0.1
 * Requires at least: 6.3
 * Requires PHP: 8.0
 * Requires Plugins: woocommerce
 * Author:      Harbor Plugins
 * Author URI:  https://harborplugins.com/
 * License:     GPL-2.0-or-later
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: harbor-equipment-rental-for-woocommerce
 * Domain Path: /languages
 *
 * WC requires at least: 8.0
 * WC tested up to:      10.9
 *
    *
 * @package WooEquipmentRental
 */

declare( strict_types=1 );

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

define( 'WCRENT_VERSION', '1.0.1' );
define( 'WCRENT_PLUGIN_FILE', __FILE__ );
define( 'WCRENT_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'WCRENT_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
define( 'WCRENT_FLATPICKR_VERSION', '4.6.13' );

// ---------------------------------------------------------------------------
// HPOS compatibility declaration
// ---------------------------------------------------------------------------

add_action(
	'before_woocommerce_init',
	function () {
		if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
			\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
				'custom_order_tables',
				__FILE__,
				true
			);
			// Block cart/checkout is fully supported; the product-page date picker works inside the add-to-cart form block.
			\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
				'cart_checkout_blocks',
				__FILE__,
				true
			);
		}
	}
);

// ---------------------------------------------------------------------------
// Freemius (licensing / premium gating)
// ---------------------------------------------------------------------------

require_once WCRENT_PLUGIN_DIR . 'includes/class-wcrent-freemius.php';
WCRent_Freemius::init();

/**
 * Returns true when the paid (Pro) licence is active.
 *
 * Resolves through the Freemius SDK when configured; the `wcrent_is_pro`
 * filter allows overriding in tests and local development.
 *
 * @return bool
 */
function wcrent_is_pro(): bool {
	$fs     = WCRent_Freemius::instance();
	$is_pro = ( null !== $fs ) && $fs->is_premium() && $fs->can_use_premium_code();
	return (bool) apply_filters( 'wcrent_is_pro', $is_pro );
}

// ---------------------------------------------------------------------------
// Boot
// ---------------------------------------------------------------------------

add_action( 'plugins_loaded', 'wcrent_init', 10 );

/**
 * Boots the plugin after all plugins are loaded.
 * Aborts gracefully when WooCommerce is not active.
 */
function wcrent_init(): void {
	if ( ! class_exists( 'WooCommerce' ) ) {
		add_action(
			'admin_notices',
			function () {
				echo '<div class="notice notice-error"><p>';
				echo wp_kses_post(
					sprintf(
						/* translators: %s: WooCommerce plugin link */
						__( '<strong>Harbor Equipment Rental for WooCommerce</strong> requires %s to be installed and active.', 'harbor-equipment-rental-for-woocommerce' ),
						'<a href="https://wordpress.org/plugins/woocommerce/">WooCommerce</a>'
					)
				);
				echo '</p></div>';
			}
		);
		return;
	}

	// Load all class files except the email subclass (WC_Email not yet available).
	$includes = array(
		'class-wcrent-utils.php',
		'class-rental-availability.php',
		'class-rental-product.php',
		'class-rental-cart.php',
		'class-rental-price.php',
		'class-rental-order.php',
		'class-admin-settings.php',
		'class-admin-rental-orders.php',
		'class-rental-blocks.php',
	);

	foreach ( $includes as $file ) {
		require_once WCRENT_PLUGIN_DIR . 'includes/' . $file;
	}

	// Premium classes live in dedicated files that are stripped from the free
	// build, so their absence must not fatal.
	$pro_includes = array(
		'class-rental-deposit.php',
		'class-rental-calendar.php',
		'class-rental-ical.php',
		'class-rental-emails.php',
	);

	foreach ( $pro_includes as $file ) {
		if ( file_exists( WCRENT_PLUGIN_DIR . 'includes/' . $file ) ) {
			require_once WCRENT_PLUGIN_DIR . 'includes/' . $file;
		}
	}

	// Initialise classes.
	WCRent_Rental_Availability::register_cache_invalidation_hooks();
	WCRent_Rental_Product::init();
	WCRent_Rental_Cart::init();
	WCRent_Rental_Price::init();
	WCRent_Rental_Order::init();
	WCRent_Admin_Settings::init();
	WCRent_Admin_Rental_Orders::init();
	WCRent_Rental_Blocks::init();

	if ( wcrent_is_pro() && class_exists( 'WCRent_Rental_Deposit' ) ) {
		WCRent_Rental_Deposit::init();
		WCRent_Rental_Calendar::init();
		WCRent_Rental_ICal::init();
		// The email cron action handler is registered directly so it fires on
		// wp-cron.php requests, where woocommerce_email_classes never fires.
		WCRent_Rental_Emails::init();
		WCRent_Rental_Emails::maybe_schedule_reminder_cron();
		// Register WCRent_Email_Return_Reminder with WC's mailer. The subclass is
		// loaded lazily inside this callback because WC_Email is not defined yet
		// at plugins_loaded (it loads when WooCommerce initialises its mailer).
		add_filter( 'woocommerce_email_classes', array( 'WCRent_Rental_Emails', 'register_email_class' ) );
	}

	// Frontend enqueueing.
	add_action( 'wp_enqueue_scripts', 'wcrent_enqueue_frontend_assets' );

	// REST API.
	add_action( 'rest_api_init', 'wcrent_register_rest_routes' );
}

// ---------------------------------------------------------------------------
// Frontend assets
// ---------------------------------------------------------------------------

/**
 * Enqueues Flatpickr and the rental product script on single product pages
 * when the product has rental enabled.
 */
function wcrent_enqueue_frontend_assets(): void {
	if ( ! is_product() ) {
		return;
	}

	global $post;

	if ( ! $post instanceof WP_Post ) {
		return;
	}

	$product_id = (int) $post->ID;
	if ( ! WCRent_Utils::is_rental_product( $product_id ) ) {
		return;
	}

	// Flatpickr (bundled locally).
	wp_enqueue_style(
		'wcrent-flatpickr',
		WCRENT_PLUGIN_URL . 'assets/flatpickr.min.css',
		array(),
		WCRENT_FLATPICKR_VERSION
	);

	wp_enqueue_script(
		'wcrent-flatpickr',
		WCRENT_PLUGIN_URL . 'assets/flatpickr.min.js',
		array(),
		WCRENT_FLATPICKR_VERSION,
		true
	);

	// Product rental stylesheet.
	wp_enqueue_style(
		'wcrent-product',
		WCRENT_PLUGIN_URL . 'assets/product-rental.css',
		array( 'wcrent-flatpickr' ),
		WCRENT_VERSION
	);

	// Product rental script.
	wp_enqueue_script(
		'wcrent-product',
		WCRENT_PLUGIN_URL . 'assets/product-rental.js',
		array( 'wcrent-flatpickr' ),
		WCRENT_VERSION,
		true
	);

	$availability_check_enabled = 'yes' === get_option( 'wcrent_enable_availability_check', 'yes' );

	// Build booked date ranges to pass to Flatpickr.
	$booked_dates_js = array();

	if ( $availability_check_enabled ) {
		$availability    = new WCRent_Rental_Availability();
		$booked_dates_js = $availability->get_booked_ranges_for_js( $product_id );
	}

	$min_days = (int) get_post_meta( $product_id, '_rental_min_days', true );
	$max_days = (int) get_post_meta( $product_id, '_rental_max_days', true );
	// Display format only (flatpickr altFormat); the picker parses and submits
	// Y-m-d regardless, which is what the server-side validation expects.
	$date_format = get_option( 'wcrent_date_format', 'M j, Y' );

	// -1 sentinel when no daily rate is configured, so the JS price preview can
	// mirror the weekly-only proration in WCRent_Rental_Price::get_base_rental_price().
	$price_day_meta = get_post_meta( $product_id, '_rental_price_day', true );
	$price_day_js   = ( '' !== $price_day_meta ) ? (float) $price_day_meta : -1;

	$wcrent_product = wc_get_product( $product_id );
	$price_base_js  = $wcrent_product ? (float) $wcrent_product->get_price() : 0.0;

	wp_localize_script(
		'wcrent-product',
		'wcRentData',
		array(
			'rest_url'                   => esc_url_raw( rest_url( 'wcrent/v1/availability' ) ),
			'nonce'                      => wp_create_nonce( 'wcrent_availability' ),
			'product_id'                 => $product_id,
			'booked_dates'               => $booked_dates_js,
			'availability_check_enabled' => $availability_check_enabled,
			'min_rental_days'            => $min_days,
			'max_rental_days'            => $max_days,
			'price_per_day'              => $price_day_js,
			'price_per_hour'             => (float) get_post_meta( $product_id, '_rental_price_hour', true ),
			'price_per_week'             => (float) get_post_meta( $product_id, '_rental_price_week', true ),
			// Base price fallback, so the JS preview matches what the cart charges
			// when neither a daily nor a weekly rate applies.
			'price_base'                 => $price_base_js,
			'currency_symbol'            => get_woocommerce_currency_symbol(),
			'currency_pos'               => get_option( 'woocommerce_currency_pos', 'left' ),
			'decimals'                   => (int) get_option( 'woocommerce_price_num_decimals', 2 ),
			'decimal_sep'                => get_option( 'woocommerce_price_decimal_sep', '.' ),
			'thousand_sep'               => get_option( 'woocommerce_price_thousand_sep', ',' ),
			'date_format'                => $date_format,
			// "Today" in the site timezone so the picker's minimum matches the
			// server-side past-date check (server uses new DateTimeImmutable('today')
			// in site tz). Relying on the browser's local date rejects otherwise-valid
			// selections for shoppers in a timezone ahead of the store.
			'today'                      => wp_date( 'Y-m-d' ),
			'billing_method'             => WCRent_Utils::billing_method(),
			'i18n'                       => array(
				'select_dates'        => __( 'Select rental dates', 'harbor-equipment-rental-for-woocommerce' ),
				'unavailable'         => __( 'Unavailable', 'harbor-equipment-rental-for-woocommerce' ),
				'rental_label'        => __( 'Rental', 'harbor-equipment-rental-for-woocommerce' ),
				'total_label'         => __( 'Total', 'harbor-equipment-rental-for-woocommerce' ),
				'rental_night'        => __( 'rental night', 'harbor-equipment-rental-for-woocommerce' ),
				'rental_nights'       => __( 'rental nights', 'harbor-equipment-rental-for-woocommerce' ),
				'calendar_day'        => __( 'calendar day', 'harbor-equipment-rental-for-woocommerce' ),
				'calendar_days_label' => __( 'calendar days', 'harbor-equipment-rental-for-woocommerce' ),
				'select_dates_error'  => __( 'Please select rental dates before adding to cart.', 'harbor-equipment-rental-for-woocommerce' ),
				'min_days_notice'     => sprintf(
					/* translators: %d: minimum number of rental days */
					_n(
						'Minimum rental period is %d day.',
						'Minimum rental period is %d days.',
						( $min_days ) ? $min_days : 1,
						'harbor-equipment-rental-for-woocommerce'
					),
					( $min_days ) ? $min_days : 1
				),
				'max_days_notice'     => $max_days > 0 ? sprintf(
					/* translators: %d: maximum rental days */
					_n(
						'Maximum rental period is %d day.',
						'Maximum rental period is %d days.',
						$max_days,
						'harbor-equipment-rental-for-woocommerce'
					),
					$max_days
				) : '',
				'hour'                => __( 'hour', 'harbor-equipment-rental-for-woocommerce' ),
				'hours'               => __( 'hours', 'harbor-equipment-rental-for-woocommerce' ),
				'select_time_error'   => __( 'Please select start and end times for a same-day rental.', 'harbor-equipment-rental-for-woocommerce' ),
				'time_range_error'    => __( 'End time must be after start time.', 'harbor-equipment-rental-for-woocommerce' ),
				'availability_error'  => __( 'Could not confirm availability — please proceed or try again.', 'harbor-equipment-rental-for-woocommerce' ),
			),
		)
	);
}

// ---------------------------------------------------------------------------
// REST API
// ---------------------------------------------------------------------------

/**
 * Permission check for the availability REST endpoint.
 *
 * The endpoint is intentionally public: it exposes only the booked (greyed-out)
 * date ranges already visible on the product page, with per-booking quantities
 * stripped. A nonce would be baked into the page HTML at render time and break
 * under full-page caching (WP Rocket, Cloudflare APO, LiteSpeed), 403-ing every
 * cached visitor. Read-only public data does not need CSRF protection.
 *
 * @return bool
 */
function wcrent_availability_permission_check(): bool {
	return true;
}

/**
 * Registers the availability REST endpoint.
 */
function wcrent_register_rest_routes(): void {
	register_rest_route(
		'wcrent/v1',
		'/availability',
		array(
			'methods'             => WP_REST_Server::READABLE,
			'callback'            => 'wcrent_rest_availability',
			'permission_callback' => 'wcrent_availability_permission_check',
			'args'                => array(
				// Kept for backward compatibility with older cached pages that
				// still send it; no longer required (endpoint is public read-only).
				'nonce'      => array(
					'required'          => false,
					'sanitize_callback' => 'sanitize_text_field',
				),
				'product_id' => array(
					'required'          => true,
					'validate_callback' => fn( $v ) => is_numeric( $v ) && (int) $v > 0,
					'sanitize_callback' => 'absint',
				),
				'start'      => array(
					'required'          => false,
					'validate_callback' => fn( $v ) => (bool) preg_match( '/^\d{4}-\d{2}-\d{2}$/', $v ),
					'sanitize_callback' => 'sanitize_text_field',
				),
				'end'        => array(
					'required'          => false,
					'validate_callback' => fn( $v ) => (bool) preg_match( '/^\d{4}-\d{2}-\d{2}$/', $v ),
					'sanitize_callback' => 'sanitize_text_field',
				),
				'qty'        => array(
					'required'          => false,
					'default'           => 1,
					'validate_callback' => fn( $v ) => is_numeric( $v ) && (int) $v > 0,
					'sanitize_callback' => 'absint',
				),
			),
		)
	);
}

/**
 * Handles GET /wp-json/wcrent/v1/availability.
 *
 * @param WP_REST_Request $request Incoming REST request.
 * @return WP_REST_Response
 */
function wcrent_rest_availability( WP_REST_Request $request ): WP_REST_Response {
	$product_id = $request->get_param( 'product_id' );
	$start      = $request->get_param( 'start' );
	$end        = $request->get_param( 'end' );
	$qty_needed = (int) $request->get_param( 'qty' );

	// Only serve data for rental-enabled products.
	if ( ! WCRent_Utils::is_rental_product( $product_id ) ) {
		return new WP_REST_Response(
			array(
				'code'    => 'not_rental_product',
				'message' => __( 'Product not found or not rental-enabled.', 'harbor-equipment-rental-for-woocommerce' ),
			),
			404
		);
	}

	$availability = new WCRent_Rental_Availability();
	$booked       = $availability->get_booked_date_ranges( $product_id );

	// Strip qty from the public response — booking quantities are internal data.
	$booked_public = array_map(
		static fn( array $r ): array => array(
			'start' => $r['start'],
			'end'   => $r['end'],
		),
		$booked
	);

	$data = array(
		'product_id'   => $product_id,
		'booked_dates' => $booked_public,
	);

	if ( $start && $end ) {
		if ( ! WCRent_Utils::is_valid_date( $start ) || ! WCRent_Utils::is_valid_date( $end ) || $end < $start ) {
			return new WP_REST_Response(
				array(
					'code'    => 'invalid_dates',
					'message' => __( 'Invalid or out-of-order dates supplied.', 'harbor-equipment-rental-for-woocommerce' ),
				),
				400
			);
		}

		$data['is_available'] = $availability->is_available(
			$product_id,
			$start,
			$end,
			$qty_needed
		);
	}

	return new WP_REST_Response( $data, 200 );
}

// ---------------------------------------------------------------------------
// Activation / Deactivation hooks
// ---------------------------------------------------------------------------

register_activation_hook(
	__FILE__,
	static function () {
		// Schedule return reminder cron on activation when Pro is active.
		// maybe_schedule_reminder_cron() also runs on every plugins_loaded via
		// wcrent_init(), so this is just an eager first-run convenience.
		if ( wcrent_is_pro() && class_exists( 'WCRent_Rental_Emails' ) ) {
			WCRent_Rental_Emails::maybe_schedule_reminder_cron();
		}
	}
);

register_deactivation_hook(
	__FILE__,
	static function () {
		wp_clear_scheduled_hook( 'wcrent_send_return_reminders' );
	}
);
