<?php
/**
 * Appointment Duration Utility
 *
 * Provides structured, type-safe methods for duration calculations,
 * conversions, and formatting. Consolidates duration-related operations
 * from various functions into a single utility class.
 *
 * @package WooCommerce Appointments
 * @since 5.1.0
 */

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

/**
 * WC_Appointment_Duration class.
 *
 * Handles duration parameter calculations, conversions, and formatting.
 * Provides a structured, type-safe API for duration operations.
 *
 * @since 5.1.0
 */
class WC_Appointment_Duration {

	/**
	 * Duration value (e.g., 1, 2, 30).
	 *
	 * @var int
	 */
	private $duration;

	/**
	 * Duration unit (e.g., 'minute', 'hour', 'day', 'month').
	 *
	 * @var string
	 */
	private $duration_unit;

	/**
	 * Constructor.
	 *
	 * @since 5.1.0
	 *
	 * @param int    $duration      Duration value.
	 * @param string $duration_unit Duration unit constant from WC_Appointments_Constants.
	 */
	public function __construct( int $duration, string $duration_unit ) {
		$this->duration      = $duration;
		$this->duration_unit = $duration_unit;
	}

	/**
	 * Get duration value.
	 *
	 * @since 5.1.0
	 *
	 * @return int Duration value.
	 */
	public function get_duration(): int {
		return $this->duration;
	}

	/**
	 * Get duration unit.
	 *
	 * @since 5.1.0
	 *
	 * @return string Duration unit constant.
	 */
	public function get_duration_unit(): string {
		return $this->duration_unit;
	}

	/**
	 * Get duration in minutes.
	 *
	 * Converts the duration to total minutes using the unit.
	 *
	 * @since 5.1.0
	 *
	 * @return int Duration in minutes.
	 */
	public function to_minutes(): int {
		return WC_Appointments_Constants::duration_unit_to_minutes( $this->duration_unit, $this->duration );
	}

	/**
	 * Get duration in seconds.
	 *
	 * Converts the duration to total seconds using the unit.
	 *
	 * @since 5.1.0
	 *
	 * @return int Duration in seconds.
	 */
	public function to_seconds(): int {
		return WC_Appointments_Constants::duration_unit_to_seconds( $this->duration_unit, $this->duration );
	}

	/**
	 * Format duration as human-readable string.
	 *
	 * Formats the duration based on the unit, with compound formatting
	 * (e.g., "2 days 3 hours 30 minutes").
	 *
	 * @since 5.1.0
	 *
	 * @return string Formatted duration string (e.g., "2 hours", "2 days 3 hours 30 minutes").
	 */
	public function to_pretty_string(): string {
		$minutes = $this->to_minutes();
		return self::format_minutes( $minutes, $this->duration_unit );
	}

	/**
	 * Format duration as addon string with +/- prefix.
	 *
	 * Formats the duration with a positive/negative prefix for addon display.
	 *
	 * @since 5.1.0
	 *
	 * @param bool $is_negative Optional. Whether to show negative prefix. Default false.
	 *
	 * @return string Formatted duration with prefix (e.g., "+2 hours", "-30 minutes").
	 */
	public function to_addon_string( bool $is_negative = false ): string {
		$prefix    = $is_negative ? '<span class="amount-symbol">-</span>' : '<span class="amount-symbol">+</span>';
		$formatted = $this->to_pretty_string();
		return '<span class="addon-duration">' . $prefix . $formatted . '</span>';
	}

	/**
	 * Convert to array format (for backward compatibility).
	 *
	 * @since 5.1.0
	 *
	 * @return array<string, int|string> {
	 *     @type int    $duration      Duration value.
	 *     @type string $duration_unit Duration unit.
	 * }
	 */
	public function to_array(): array {
		return [
			'duration'      => $this->duration,
			'duration_unit' => $this->duration_unit,
		];
	}

	/**
	 * Create from minutes.
	 *
	 * Converts minutes to the most appropriate duration unit and creates a duration object.
	 * This is the core logic extracted from wc_appointment_duration_parameters_result().
	 *
	 * @since 5.1.0
	 *
	 * @param int $minutes Time in minutes to convert.
	 *
	 * @return WC_Appointment_Duration Duration object.
	 */
	public static function from_minutes( int $minutes ): self {
		$mins_per_month = apply_filters( 'woocommerce_appointments_month_duration_break', 60 * 24 * 30 ); // 1 month.
		$mins_per_day   = apply_filters( 'woocommerce_appointments_day_duration_break', 60 * 24 ); // 1 day.
		$mins_per_hour  = apply_filters( 'woocommerce_appointments_hour_duration_break', 60 * 2 ); // 2 hours.

		$duration      = 1;
		$duration_unit = WC_Appointments_Constants::DURATION_MINUTE;

		// Months.
		if ( $minutes >= $mins_per_month ) {
			$months = floor( $minutes / 43200 );

			$duration      = intval( $months );
			$duration_unit = WC_Appointments_Constants::DURATION_MONTH;

			$months_in_minuts = $months * 43200;
			$days             = floor( ( $minutes - $months_in_minuts ) / 7200 );
			if ( 0 < $days ) {
				$days = floor( $minutes / 1440 );

				$duration      = intval( $days );
				$duration_unit = WC_Appointments_Constants::DURATION_DAY;
			}
		// Days.
		} elseif ( $minutes >= $mins_per_day ) {
			$days = floor( $minutes / 1440 );

			$duration      = intval( $days );
			$duration_unit = WC_Appointments_Constants::DURATION_DAY;

			$days_in_minuts = $days * 1440;
			$hours          = floor( ( $minutes - $days_in_minuts ) / 60 );
			if ( 0 < $hours ) {
				$hours = floor( $minutes / 60 );

				$duration      = intval( $hours );
				$duration_unit = WC_Appointments_Constants::DURATION_HOUR;
			}

			$minutes_remainder = ( $minutes % 60 );
			if ( 0 < $minutes_remainder ) {
				$duration      = intval( $minutes );
				$duration_unit = WC_Appointments_Constants::DURATION_MINUTE;
			}
		// Hours.
		} elseif ( $minutes >= $mins_per_hour ) {
			$hours = floor( $minutes / 60 );

			$duration      = intval( $hours );
			$duration_unit = WC_Appointments_Constants::DURATION_HOUR;

			$minutes_remainder = ( $minutes % 60 );
			if ( 0 < $minutes_remainder ) {
				$duration      = intval( $minutes );
				$duration_unit = WC_Appointments_Constants::DURATION_MINUTE;
			}
		// Minutes.
		} else {
			$duration      = intval( $minutes );
			$duration_unit = WC_Appointments_Constants::DURATION_MINUTE;
		}

		return new self( $duration, $duration_unit );
	}

	/**
	 * Create from array (for backward compatibility).
	 *
	 * @since 5.1.0
	 *
	 * @param array<string, int|string> $data {
	 *     @type int    $duration      Duration value.
	 *     @type string $duration_unit Duration unit.
	 * }
	 *
	 * @return WC_Appointment_Duration Duration object.
	 */
	public static function from_array( array $data ): self {
		return new self(
			isset( $data['duration'] ) ? (int) $data['duration'] : 1,
			isset( $data['duration_unit'] ) ? (string) $data['duration_unit'] : WC_Appointments_Constants::DURATION_MINUTE
		);
	}

	/**
	 * Calculate duration between two timestamps.
	 *
	 * Calculates the duration in minutes between start and end timestamps.
	 *
	 * @since 5.1.0
	 *
	 * @param int $start_timestamp Start timestamp.
	 * @param int $end_timestamp   End timestamp.
	 *
	 * @return int Duration in minutes.
	 */
	public static function calculate_minutes( int $start_timestamp, int $end_timestamp ): int {
		if ( ! $start_timestamp || ! $end_timestamp ) {
			return 0;
		}

		$start_time = round( $start_timestamp / 60 ) * 60; // Round to nearest minute.
		$end_time   = round( $end_timestamp / 60 ) * 60; // Round to nearest minute.
		$time_diff  = abs( $end_time - $start_time );

		return (int) ( $time_diff / 60 );
	}

	/**
	 * Format minutes as human-readable string with compound formatting.
	 *
	 * Formats a duration in minutes as a human-readable string based on the duration unit.
	 * Supports compound formatting (e.g., "2 days 3 hours 30 minutes").
	 * Matches the behavior of wc_appointment_pretty_timestamp().
	 *
	 * @since 5.1.0
	 *
	 * @param int    $minutes      Duration in minutes.
	 * @param string $duration_unit Optional. Duration unit for formatting. Default 'minute'.
	 *
	 * @return string Formatted duration string (e.g., "2 hours", "2 days 3 hours 30 minutes").
	 */
	public static function format_minutes( int $minutes, string $duration_unit = WC_Appointments_Constants::DURATION_MINUTE ): string {
		$duration_unit = is_string( $duration_unit ) && '' !== $duration_unit ? $duration_unit : WC_Appointments_Constants::DURATION_MINUTE;
		if ( ! in_array( $duration_unit, WC_Appointments_Constants::get_duration_units(), true ) ) {
			$duration_unit = WC_Appointments_Constants::DURATION_MINUTE;
		}

		$mins_per_month = apply_filters( 'woocommerce_appointments_month_duration_break', 60 * 24 * 30 ); // 1 month.
		$mins_per_day   = apply_filters( 'woocommerce_appointments_day_duration_break', 60 * 24 ); // 1 day.
		$mins_per_hour  = apply_filters( 'woocommerce_appointments_hour_duration_break', 60 * 2 ); // 2 hours.

		// Months.
		if ( $minutes >= $mins_per_month && WC_Appointments_Constants::DURATION_MONTH === $duration_unit ) {
			$months           = floor( $minutes / 43200 );
			$months_in_minuts = $months * 43200;
			/* translators: %s: months in singular or plural */
			$return = sprintf( _n( '%s month', '%s months', $months, 'woocommerce-appointments' ), $months );
			$days   = floor( ( $minutes - $months_in_minuts ) / 7200 );
			if ( 0 < $days ) {
				$return .= '&nbsp;'; // Empty space.
				/* translators: %s: days in singular or plural */
				$return .= sprintf( _n( '%s day', '%s days', $days, 'woocommerce-appointments' ), $days );
			}
			return apply_filters( 'wc_appointment_pretty_timestamp', $return, $minutes );
		}

		// Days by calculation.
		if ( $minutes >= $mins_per_day || WC_Appointments_Constants::DURATION_DAY === $duration_unit ) {
			$days           = floor( $minutes / 1440 );
			$days_in_minuts = $days * 1440;
			/* translators: %s: days in singular or plural */
			$return = sprintf( _n( '%s day', '%s days', $days, 'woocommerce-appointments' ), $days );
			$hours  = floor( ( $minutes - $days_in_minuts ) / 60 );
			if ( 0 < $hours ) {
				$return .= '&nbsp;'; // Empty space.
				/* translators: %s: hours in singular or plural */
				$return .= sprintf( _n( '%s hour', '%s hours', $hours, 'woocommerce-appointments' ), $hours );
			}
			$minutes_remainder = ( $minutes % 60 );
			if ( 0 < $minutes_remainder ) {
				$return .= '&nbsp;'; // Empty space.
				/* translators: %s: minutes */
				$return .= sprintf( _n( '%s minute', '%s minutes', $minutes_remainder, 'woocommerce-appointments' ), $minutes_remainder );
			}
			return apply_filters( 'wc_appointment_pretty_timestamp', $return, $minutes );
		}

		// Hours by calculation.
		if ( $minutes >= $mins_per_hour ) {
			$hours = floor( $minutes / 60 );
			/* translators: %s: hours in singular or plural */
			$return  = sprintf( _n( '%s hour', '%s hours', $hours, 'woocommerce-appointments' ), $hours );
			$minutes_remainder = ( $minutes % 60 );
			if ( 0 < $minutes_remainder ) {
				$return .= '&nbsp;'; // Empty space.
				/* translators: %s: minutes */
				$return .= sprintf( _n( '%s minute', '%s minutes', $minutes_remainder, 'woocommerce-appointments' ), $minutes_remainder );
			}
			return apply_filters( 'wc_appointment_pretty_timestamp', $return, $minutes );
		}

		// Minutes by calculation.
		/* translators: %s: minutes */
		$return = sprintf( _n( '%s minute', '%s minutes', $minutes, 'woocommerce-appointments' ), $minutes );
		return apply_filters( 'wc_appointment_pretty_timestamp', $return, $minutes );
	}

	/**
	 * Format addon duration with +/- prefix.
	 *
	 * Formats duration for addon display, handling product-specific duration units.
	 * Matches the behavior of wc_appointment_pretty_addon_duration().
	 *
	 * @since 5.1.0
	 *
	 * @param int             $time         Duration in minutes (can be negative).
	 * @param WC_Product|null $product      Optional. Product object to get duration unit from.
	 *
	 * @return string|false Formatted addon duration with prefix, or false if time is empty/zero.
	 */
	public static function format_addon_duration( int $time, $product = null ): string|false {
		if ( '' === $time || '0' == $time ) {
			return false;
		}

		$is_negative = 0 > $time;
		$time        = absint( $time );

		// Get duration unit from product if available.
		if ( $product && is_wc_appointment_product( $product ) ) {
			$duration_unit = $product->get_duration_unit() ?: WC_Appointments_Constants::DURATION_MINUTE;
		} else {
			$duration_unit = WC_Appointments_Constants::DURATION_MINUTE;
		}

		// Format based on unit.
		if ( WC_Appointments_Constants::DURATION_MONTH === $duration_unit ) {
			/* translators: %s: months in singular or plural */
			$formatted = sprintf( _n( '%s month', '%s months', $time, 'woocommerce-appointments' ), $time );
		} elseif ( WC_Appointments_Constants::DURATION_DAY === $duration_unit ) {
			/* translators: %s: days in singular or plural */
			$formatted = sprintf( _n( '%s day', '%s days', $time, 'woocommerce-appointments' ), $time );
		} else {
			// Hourly or minutes product duration sets add-on duration in minutes.
			$formatted = self::format_minutes( $time, WC_Appointments_Constants::DURATION_MINUTE );
		}

		$prefix = $is_negative ? '<span class="amount-symbol">-</span>' : '<span class="amount-symbol">+</span>';
		$html   = '<span class="addon-duration">' . $prefix . $formatted . '</span>';

		return apply_filters( 'wc_appointment_pretty_addon_duration', $html, $time, $product );
	}
}
