<?php
/**
 * Shois Chat Button — Availability Engine
 *
 * Determines if agents are online based on schedule rules,
 * timezone, and holiday blackout dates.
 *
 * @package ShoisChatButton
 */

// Prevent direct access.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class SHCB_Availability
 *
 * Checks agent and global availability based on schedules.
 */
class SHCB_Availability {

	/**
	 * Check if the widget should show as online right now.
	 *
	 * @param array $schedule Schedule config from settings.
	 * @return bool True if online.
	 */
	public static function is_online( $schedule ) {
		if ( empty( $schedule['enabled'] ) ) {
			return true; // No schedule = always online.
		}

		$timezone = isset( $schedule['timezone'] ) ? $schedule['timezone'] : 'UTC';
		$hours    = isset( $schedule['hours'] ) ? $schedule['hours'] : array();

		// Check holiday blackout.
		if ( self::is_holiday_blackout( $schedule, $timezone ) ) {
			return false;
		}

		return self::is_within_hours( $hours, $timezone );
	}

	/**
	 * Check if an individual agent is available.
	 *
	 * @param array  $agent    Agent config.
	 * @param string $timezone Timezone string.
	 * @return bool True if agent is available.
	 */
	public static function is_agent_available( $agent, $timezone = 'UTC' ) {
		// If agent has no schedule, fall through to global.
		if ( empty( $agent['schedule']['enabled'] ) ) {
			return true;
		}

		$hours = isset( $agent['schedule']['hours'] ) ? $agent['schedule']['hours'] : array();
		return self::is_within_hours( $hours, $timezone );
	}

	/**
	 * Check if current time is within operating hours.
	 *
	 * @param array  $hours    Operating hours keyed by day (0-6, 0=Sunday).
	 * @param string $timezone Timezone string.
	 * @return bool True if within hours.
	 */
	private static function is_within_hours( $hours, $timezone ) {
		if ( empty( $hours ) ) {
			return true;
		}

		try {
			$tz  = new DateTimeZone( $timezone );
			$now = new DateTime( 'now', $tz );
		} catch ( \Exception $e ) {
			return true; // Fail open on invalid timezone.
		}

		$day_of_week = (int) $now->format( 'w' ); // 0=Sun, 6=Sat.
		$current_time = $now->format( 'H:i' );

		$day_key = (string) $day_of_week;

		// Check if day exists in schedule.
		if ( ! isset( $hours[ $day_key ] ) ) {
			return false; // No hours set = closed.
		}

		$day_schedule = $hours[ $day_key ];

		// Day is explicitly disabled.
		if ( empty( $day_schedule['enabled'] ) ) {
			return false;
		}

		$start = isset( $day_schedule['start'] ) ? $day_schedule['start'] : '00:00';
		$end   = isset( $day_schedule['end'] ) ? $day_schedule['end'] : '23:59';

		// Handle overnight schedules (e.g., 22:00 - 06:00).
		if ( $end < $start ) {
			return ( $current_time >= $start || $current_time <= $end );
		}

		return ( $current_time >= $start && $current_time <= $end );
	}

	/**
	 * Check if today is a holiday blackout date.
	 *
	 * @param array  $schedule Schedule config.
	 * @param string $timezone Timezone string.
	 * @return bool True if today is a blackout date.
	 */
	private static function is_holiday_blackout( $schedule, $timezone ) {
		if ( empty( $schedule['blackout_dates'] ) ) {
			return false;
		}

		try {
			$tz    = new DateTimeZone( $timezone );
			$today = new DateTime( 'now', $tz );
			$today_str = $today->format( 'Y-m-d' );
		} catch ( \Exception $e ) {
			return false;
		}

		$blackouts = $schedule['blackout_dates'];

		if ( ! is_array( $blackouts ) ) {
			return false;
		}

		foreach ( $blackouts as $date ) {
			$sanitized = sanitize_text_field( $date );
			if ( $sanitized === $today_str ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Get the next available time for display in offline message.
	 *
	 * @param array  $hours    Operating hours.
	 * @param string $timezone Timezone string.
	 * @return string|null Next opening time or null.
	 */
	public static function get_next_available( $hours, $timezone = 'UTC' ) {
		if ( empty( $hours ) ) {
			return null;
		}

		try {
			$tz  = new DateTimeZone( $timezone );
			$now = new DateTime( 'now', $tz );
		} catch ( \Exception $e ) {
			return null;
		}

		$day_names = array(
			__( 'Sunday', 'shois-chat-button' ),
			__( 'Monday', 'shois-chat-button' ),
			__( 'Tuesday', 'shois-chat-button' ),
			__( 'Wednesday', 'shois-chat-button' ),
			__( 'Thursday', 'shois-chat-button' ),
			__( 'Friday', 'shois-chat-button' ),
			__( 'Saturday', 'shois-chat-button' ),
		);

		$current_day = (int) $now->format( 'w' );

		// Check next 7 days.
		for ( $i = 0; $i < 7; $i++ ) {
			$check_day = ( $current_day + $i ) % 7;
			$day_key   = (string) $check_day;

			if ( ! isset( $hours[ $day_key ] ) || empty( $hours[ $day_key ]['enabled'] ) ) {
				continue;
			}

			$start = isset( $hours[ $day_key ]['start'] ) ? $hours[ $day_key ]['start'] : '00:00';

			// If same day, check if the start time is still ahead.
			if ( 0 === $i ) {
				$current_time = $now->format( 'H:i' );
				if ( $current_time < $start ) {
					/* translators: %s: time string */
					return sprintf( __( 'Available today at %s', 'shois-chat-button' ), esc_html( $start ) );
				}
				continue; // Today's hours have passed.
			}

			if ( 1 === $i ) {
				/* translators: %s: time string */
				return sprintf( __( 'Available tomorrow at %s', 'shois-chat-button' ), esc_html( $start ) );
			}

			return sprintf(
				/* translators: 1: day name, 2: time string */
				__( 'Available %1$s at %2$s', 'shois-chat-button' ),
				esc_html( $day_names[ $check_day ] ),
				esc_html( $start )
			);
		}

		return null;
	}

	/**
	 * Process an array of agents and compute their availability.
	 *
	 * @param array  $agents   Array of agent configs.
	 * @param string $timezone Timezone string.
	 * @return array Agents with 'available' key updated.
	 */
	public static function process_agents_availability( $agents, $timezone = 'UTC' ) {
		$result = array();

		foreach ( $agents as $agent ) {
			$agent['available'] = self::is_agent_available( $agent, $timezone );
			$result[] = $agent;
		}

		return $result;
	}
}
