<?php
/**
 * Date and time helper functions.
 *
 * @package Awesomemotive\Rewardswp\Helpers
 * @since 1.0.0
 */

namespace Awesomemotive\Rewardswp\Helpers;

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

use DateTime;
use Exception;

/**
 * Calculates a new date by adding a specified number of days to a given date.
 *
 * If no date is passed, the current date is used.
 *
 * @since 1.0.0
 *
 * @param int         $days       The number of days to add.
 * @param string|null $start_date Optional. The starting date in 'Y-m-d H:i:s' format. Default is null (current date).
 *
 * @return string The new date in 'Y-m-d H:i:s' format.
 *
 * @throws Exception
 */
function add_days_to_date( int $days, ?string $start_date = null ): string {
	// Use the current date if no date is passed.
	if ( null === $start_date ) {
		$start_date = get_current_time();
	}

	// Create a DateTime object from the start date.
	$date = new DateTime( $start_date );

	// Add the specified number of days.
	$date->modify( "+{$days} days" );

	// Return the new date in 'Y-m-d H:i:s' format.
	return $date->format( 'Y-m-d H:i:s' );
}

/**
 * Calculates a new date by adding a specified time string.
 *
 * If no date is passed, the current date is used.
 *
 * @since 1.0.0
 *
 * @param string      $time       A string representing the amount of time to add. E.g., '7 days', '3 months', etc.
 * @param string|null $start_date Optional. The starting date in 'Y-m-d H:i:s' format. Default is null (current date).
 *
 * @return string The new date in 'Y-m-d H:i:s' format.
 *
 * @throws Exception If the date or time string is invalid.
 */
function add_time_to_date( string $time, ?string $start_date = null ): string {
	// Use the current date and time if no start date is provided.
	if ( null === $start_date ) {
		$start_date = get_current_time();
	}

	// Create a DateTime object from the start date.
	$date = new DateTime( $start_date );

	// Add the specified time string.
	$date->modify( $time );

	// Return the new date in 'Y-m-d H:i:s' format.
	return $date->format( 'Y-m-d H:i:s' );
}

/**
 * Compare two dates and return the difference of the number of days between them.
 *
 * @since 1.0.0
 *
 * @param string|null $future_date A future date in mysql format. Null/empty (e.g. a
 *                                 "never expires" reward) returns null rather than erroring.
 * @param string      $prev_date   The previous date in mysql format. Default 'today'
 *
 * @return int|null The number of days.
 */
function get_date_diff_in_days( ?string $future_date, string $prev_date = 'today' ): ?int {
	if ( empty( $future_date ) ) {
		return null;
	}

	try {
		$future_date_obj = new DateTime(
			$future_date,
			'today' === $future_date
				? wp_timezone()
				: null
		);

		$prev_date_obj = new DateTime(
			$prev_date,
			'today' === $prev_date
				? wp_timezone()
				: null
		);

		$interval = $prev_date_obj->diff( $future_date_obj );

		return $interval->days;
	} catch ( Exception $exception ) {
		return null;
	}
}

/**
 * Retrieve a date in a human-readable format.
 *
 * @since 1.0.0
 *
 * @param string $date       The date to compare. The date should be passed using the site's timezone.
 * @param bool   $capitalize Whether to capitalize relative dates (today, yesterday). Default false.
 * @param bool   $escape     Whether to HTML-escape the result. Default true. Pass false ONLY when the
 *                           value is consumed in a non-HTML sink such as Alpine x-text (textContent)
 *                           or a JSON payload; the caller is then responsible for escaping at output.
 *
 * @return string
 *
 * @throws Exception
 */
function get_human_readable_date( string $date, bool $capitalize = false, bool $escape = true ): string {
	$current_date = new DateTime( 'now', wp_timezone() );
	$target_date  = new DateTime( $date, wp_timezone() );

	$interval = $current_date->diff( $target_date );

	if ( $current_date->format( 'Y-m-d' ) === $target_date->format( 'Y-m-d' ) ) {
		$text = $escape ? esc_html__( 'today', 'rewardswp' ) : __( 'today', 'rewardswp' );
		return $capitalize ? ucfirst( $text ) : $text;
	}

	$yesterday = ( clone $current_date )->modify( '-1 day' );

	if ( $yesterday->format( 'Y-m-d' ) === $target_date->format( 'Y-m-d' ) ) {
		$text = $escape ? esc_html__( 'yesterday', 'rewardswp' ) : __( 'yesterday', 'rewardswp' );
		return $capitalize ? ucfirst( $text ) : $text;
	}

	if ( $interval->days < 7 ) {
		/* translators: %d is a placeholder for the number of days */
		$relative = _n( '%d day ago', '%d days ago', $interval->days, 'rewardswp' );
		return sprintf( $escape ? esc_html( $relative ) : $relative, $interval->days );
	}

	$formatted = wp_date( 'M j, Y', $target_date->getTimestamp() );
	return $escape ? esc_html( $formatted ) : $formatted;
}


/**
 * Wrapper function for the WP current time.
 *
 * @since 1.0.0
 *
 * @param string   $format Date format. Default 'mysql'.
 * @param int|bool $gmt    Whether to use GMT timezone. Default 0.
 *
 * @return int|string Now's date.
 */
function get_current_time( string $format = 'mysql', $gmt = 0 ) {
	return current_time( $format, $gmt );
}

/**
 * Helper function to format and display a date based on a timestamp.
 *
 * This function uses the `wp_date()` function and the site's date format as defined
 * in the WordPress general settings.
 *
 * @since 1.0.0
 *
 * @param int $timestamp The Unix timestamp to format.
 *
 * @return string The formatted date.
 */
function format_date_from_timestamp( int $timestamp ): string {
	return wp_date( get_option( 'date_format' ), $timestamp );
}

/**
 * Format a datetime as a human-readable relative time.
 *
 * Returns strings like "Just now", "5 minutes ago", "2 hours ago",
 * "Yesterday at 3:45 PM", "Monday at 9:00 AM", or "Nov 15, 2024".
 *
 * @since 1.1.0
 *
 * @param string $datetime The datetime string (expected in site's local timezone).
 *
 * @return string Formatted relative time, or empty string if invalid.
 */
function get_human_readable_time_ago( string $datetime ): string {
	if ( empty( $datetime ) ) {
		return '';
	}

	// Parse the datetime in the site's timezone.
	$timezone = wp_timezone();
	$event_dt = date_create( $datetime, $timezone );
	$now_dt   = date_create( 'now', $timezone );

	if ( false === $event_dt || false === $now_dt ) {
		return '';
	}

	$diff = $now_dt->getTimestamp() - $event_dt->getTimestamp();

	// Less than 1 minute.
	if ( $diff < 60 ) {
		return __( 'Just now', 'rewardswp' );
	}

	// Less than 1 hour.
	if ( $diff < 3600 ) {
		$minutes = (int) floor( $diff / 60 );
		return sprintf(
			/* translators: %d: number of minutes */
			_n( '%d minute ago', '%d minutes ago', $minutes, 'rewardswp' ),
			$minutes
		);
	}

	// Less than 24 hours.
	if ( $diff < 86400 ) {
		$hours = (int) floor( $diff / 3600 );
		return sprintf(
			/* translators: %d: number of hours */
			_n( '%d hour ago', '%d hours ago', $hours, 'rewardswp' ),
			$hours
		);
	}

	// Yesterday.
	$yesterday = date_create( 'yesterday', $timezone )->format( 'Y-m-d' );
	$event_day = $event_dt->format( 'Y-m-d' );

	if ( $event_day === $yesterday ) {
		return sprintf(
			/* translators: %s: time in format like "3:45 PM" */
			__( 'Yesterday at %s', 'rewardswp' ),
			$event_dt->format( 'g:i A' )
		);
	}

	// Less than 7 days - show day name.
	if ( $diff < 604800 ) {
		return sprintf(
			/* translators: 1: day name like "Monday", 2: time like "3:45 PM" */
			__( '%1$s at %2$s', 'rewardswp' ),
			$event_dt->format( 'l' ),
			$event_dt->format( 'g:i A' )
		);
	}

	// Older - show date.
	return $event_dt->format( 'M j, Y' );
}

/**
 * Format a GMT date as a relative date label.
 *
 * Returns "Today", "Yesterday", day name for 2-6 days ago,
 * or "M j" format (e.g., "Dec 5") for older dates.
 *
 * @since 1.1.0
 *
 * @param string $gmt_date The date in GMT/UTC timezone.
 *
 * @return string The relative date label.
 */
function get_relative_date_label( string $gmt_date ): string {
	if ( empty( $gmt_date ) ) {
		return '';
	}

	$local_time = get_date_from_gmt( $gmt_date, 'U' );
	$now        = current_time( 'timestamp' );
	$days_ago   = (int) floor( ( $now - $local_time ) / DAY_IN_SECONDS );

	if ( 0 === $days_ago ) {
		return __( 'Today', 'rewardswp' );
	}

	if ( 1 === $days_ago ) {
		return __( 'Yesterday', 'rewardswp' );
	}

	if ( $days_ago >= 2 && $days_ago <= 6 ) {
		return get_date_from_gmt( $gmt_date, 'l' ); // Day name (Monday, Tuesday, etc.)
	}

	return get_date_from_gmt( $gmt_date, 'M j' ); // Dec 5
}
