<?php
// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

/**
 * WooCommerce Deposits integration class.
 *
 * Last compatibility check: 2.3.7
 */
class WC_Appointments_Integration_Deposits implements WC_Appointments_Integration_Interface {

	/**
	 * Constructor
	 */
	public function __construct() {
		$this->add_hooks();
	}

	/**
	 * Register WordPress hooks for this integration.
	 *
	 * @return void
	 */
	public function add_hooks(): void {
		add_filter( 'woocommerce_payment_complete_order_status', [ $this, 'handle_partial_payment' ], 20, 2 );
		add_filter( 'woocommerce_payment_complete_order_status', [ $this, 'handle_completed_payment' ], 40, 2 );
		add_action( 'woocommerce_order_status_changed', [ $this, 'handle_order_status_changed' ], 20, 3 );
		add_action( 'init', [ $this, 'register_custom_post_status' ], 10, 0 );
		add_filter( 'woocommerce_appointments_get_wc_appointment_statuses', [ $this, 'add_custom_status' ] );
		add_filter( 'woocommerce_appointments_get_status_label', [ $this, 'add_custom_status' ], 10, 2 );
		add_filter( 'woocommerce_appointments_gcal_statuses', [ $this, 'add_custom_paid_status' ] );
		add_filter( 'woocommerce_appointments_gcal_sync_statuses', [ $this, 'add_custom_paid_status' ] );
		add_action( 'woocommerce_payment_complete', [ $this, 'save_order_status' ] );
		add_action( 'added_order_item_meta', [ $this, 'handle_remaining_balance_paid' ], 20, 4 );
		add_action( 'updated_order_item_meta', [ $this, 'handle_remaining_balance_paid' ], 20, 4 );
	}

	/**
	 * Process partial payments.
	 *
	 * Handles the order status update when a partial payment is made.
	 *
	 * @since 3.5.6
	 *
	 * @param string  $order_status Order status to be changed for filter.
	 * @param integer $order_id     Order ID.
	 * @return string Modified order status.
	 */
	public function handle_partial_payment( $order_status, $order_id ) {
		// Deposits order status support.
		if ( 'partial-payment' === $order_status ) {
			$this->set_status_for_appointments_in_order( $order_id, 'wc-partial-payment' );
		}

		return $order_status;
	}

	/**
	 * Go through all appointment for an order and update the status for each.
	 *
	 * For partial-payment status, only appointments whose order item is actually
	 * a deposit will be marked as partial-payment. Appointments with full-payment
	 * items in the same order will be marked as paid instead.
	 *
	 * @since 3.5.6
	 *
	 * @param integer $order_id To find appointments.
	 * @param string  $new_status To set to appointments of order.
	 */
	public function set_status_for_appointments_in_order( $order_id, string $new_status ): void {
		$appointment_ids = WC_Appointment_Data_Store::get_appointment_ids_from_order_id( $order_id );
		$order           = wc_get_order( $order_id );

		// Build a map of order_item_id => is_deposit for quick lookup.
		$item_deposit_map = [];
		if ( $order && class_exists( 'WC_Deposits_Order_Item_Manager' ) ) {
			foreach ( $order->get_items() as $item_id => $item ) {
				$item_deposit_map[ $item_id ] = WC_Deposits_Order_Item_Manager::is_deposit( $item );
			}
		}

		foreach ( $appointment_ids as $appointment_id ) {
			try {
				$appointment = new WC_Appointment( $appointment_id );
			} catch ( Exception $e ) {
				wc_get_logger()->error( $e->getMessage() );
				continue;
			}

			// For partial-payment status, only apply to appointments whose order item is a deposit.
			if ( 'wc-partial-payment' === $new_status ) {
				$order_item_id   = $appointment->get_order_item_id();
				$item_is_deposit = isset( $item_deposit_map[ $order_item_id ] ) ? $item_deposit_map[ $order_item_id ] : false;

				if ( ! $item_is_deposit ) {
					// This appointment's item is NOT a deposit - mark as paid instead.
					$appointment->paid();
					continue;
				}
			}

			$appointment->set_status( $new_status );
			$appointment->save();
		}
	}

	/**
	 * Process partial payments.
	 *
	 * @since 3.5.6
	 *
	 * @param string  $order_status To filter/change.
	 * @param integer $order_id To which this applies.
	 */
	public function handle_completed_payment( $order_status, $order_id ) {
		$order = wc_get_order( $order_id );
		if ( ! is_a( $order, 'WC_Order' ) ) {
			return $order_status;
		}
		if ( 'processing' !== $order_status
			|| ! $order->has_status( 'pending-deposit' ) ) {
			return $order_status;
		}

		if ( count( $order->get_items() ) < 1 ) {
			return $order_status;
		}
		$virtual_appointment_order = false;

		foreach ( $order->get_items() as $item ) {
			if ( $item->is_type( 'line_item' ) ) {
				$product                   = $item->get_product();
				$virtual_appointment_order = $product && $product->is_virtual() && $product->is_type( 'appointment' );
			}
			if ( ! $virtual_appointment_order ) {
				break;
			}
		}

		// Virtual order, mark as completed.
		if ( $virtual_appointment_order ) {
			return 'completed';
		}

		return $order_status;
	}

	/**
	 * Handle changes in the order status.
	 *
	 * @since 4.22.8
	 *
	 * @param integer $order_id Id of de order whose status is changing.
	 * @param string $from Previous order status.
	 * @param string $to New order status.
	 */
	public function handle_order_status_changed( $order_id, $from, $to ): void {
		if ( 'partial-payment' === $to ) {
			$this->set_status_for_appointments_in_order( $order_id, 'wc-' . $to );
		} elseif ( 'failed' === $to ) {
			$this->set_status_for_appointments_in_order( $order_id, 'unpaid' );
		}
	}

	/**
	 * Register the Deposits integration post status.
	 *
	 * @since 3.5.6
	 */
	public function register_custom_post_status(): void {
		if ( is_admin() && isset( $_GET['post_type'] ) && 'wc_appointment' === $_GET['post_type'] ) {
			register_post_status(
			    'wc-partial-payment',
			    [
					'label'                     => '<span class="status-partial-payment tips" data-tip="' . wc_sanitize_tooltip( _x( 'Partially Paid', 'woocommerce-appointments', 'woocommerce-appointments' ) ) . '">' . _x( 'Partially Paid', 'woocommerce-appointments', 'woocommerce-appointments' ) . '</span>',
					'public'                    => true,
					'exclude_from_search'       => false,
					'show_in_admin_all_list'    => true,
					'show_in_admin_status_list' => true,
					/* translators: %s */
					'label_count'               => _n_noop( 'Partially Paid <span class="count">(%s)</span>', 'Partially Paid <span class="count">(%s)</span>', 'woocommerce-appointments' ),
				],
			);
		}
	}

	/**
	 * Add custom status to the list of standard appointments status.
	 *
	 * @since 3.5.6
	 *
	 * @param array $statuses to be changed in this function.
	 */
	public function add_custom_status( array $statuses ): array {
		$statuses['wc-partial-payment'] = __( 'Partially Paid', 'woocommerce-appointments' );
		return $statuses;
	}

	/**
	 * Make partial payment count as paid.
	 *
	 * Adds the 'wc-partial-payment' status to the list of statuses considered as paid,
	 * ensuring items are added to Google Calendar.
	 *
	 * @param array $statuses Current paid statuses.
	 *
	 * @return array Modified statuses.
	 */
	public function add_custom_paid_status( $statuses ) {
		$statuses[] = 'wc-partial-payment';
		return $statuses;
	}

	/**
     * Saves the order status from pending to wc-partial-payment
     * so that the reminder cron job can pick it up.
     *
     * @since 4.9.2
     */
    public function save_order_status( $order_id ): void {
		$order = wc_get_order( $order_id );

		if ( 'partial-payment' === $order->get_status() ) {
			$this->set_status_for_appointments_in_order( $order_id, 'wc-partial-payment' );
		}
	}

	/**
	 * Update appointment status when remaining balance is marked as paid offline.
	 *
	 * Only marks the specific appointment(s) linked to the paid item as paid,
	 * not all appointments in the order. This handles mixed orders correctly.
	 *
	 * @param int    $meta_id   Meta ID.
	 * @param int    $item_id   Order item ID.
	 * @param string $meta_key  Meta key.
	 * @param mixed  $meta_value Meta value.
	 */
	public function handle_remaining_balance_paid( $meta_id, $item_id, $meta_key, $meta_value ): void {
		if ( '_remaining_balance_paid' !== $meta_key ) {
			return;
		}

		if ( empty( $meta_value ) ) {
			return;
		}

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

		// Get appointments linked to this specific order item.
		$appointment_ids = WC_Appointment_Data_Store::get_appointment_ids_from_order_item_id( $item_id );
		if ( empty( $appointment_ids ) ) {
			return;
		}

		foreach ( $appointment_ids as $appointment_id ) {
			$appointment = get_wc_appointment( $appointment_id );
			if ( ! $appointment ) {
				continue;
			}
			// The paid() method now checks per-item deposit status.
			$appointment->paid();
		}
	}
}
