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

class WC_Appointment_Meta_Box_Save {
	use WC_Appointments_Meta_Box_Trait;

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->id         = 'woocommerce-appointment-save';
		$this->title      = __( 'Save', 'woocommerce-appointments' );
		$this->context    = 'side';
		$this->priority   = 'high';
		$this->post_types = [ 'wc_appointment' ];

		add_action( 'woocommerce_before_appointment_object_save', [ $this, 'track_appointment_changes' ], 10, 3 );
	}

	/**
	 * Render inner part of meta box.
	 */
	public function meta_box_inner( $post ): void {
		wp_nonce_field( 'wc_appointments_save_appointment_meta_box', 'wc_appointments_save_appointment_meta_box_nonce' );

		?>
		<div class="submitbox">
			<div class="minor-save-actions">
				<div class="misc-pub-section curtime misc-pub-curtime">
					<label for="appointment_date"><?php esc_html_e( 'Created on:', 'woocommerce-appointments' ); ?></label>
					<input
						type="text"
						class="date-picker"
						name="appointment_date"
						id="appointment_date"
						maxlength="10"
						value="<?php esc_attr_e( date_i18n( 'Y-m-d', strtotime( $post->post_date ) ) ); ?>"
						pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])"
					/>
					@
					<input
						type="number"
						class="hour"
						placeholder="<?php esc_html_e( 'h', 'woocommerce-appointments' ); ?>"
						name="appointment_date_hour"
						id="appointment_date_hour"
						maxlength="2"
						size="2"
						value="<?php esc_html_e( date_i18n( 'H', strtotime( $post->post_date ) ) ); ?>"
						pattern="\-?\d+(\.\d{0,})?"
					/>:<input
						type="number"
						class="minute"
						placeholder="<?php esc_html_e( 'm', 'woocommerce-appointments' ); ?>"
						name="appointment_date_minute"
						id="appointment_date_minute"
						maxlength="2"
						size="2"
						value="<?php esc_html_e( date_i18n( 'i', strtotime( $post->post_date ) ) ); ?>"
						pattern="\-?\d+(\.\d{0,})?"
					/>
				</div>
				<div class="misc-pub-section misc-pub-note">
					<label for="appointment_note"><?php esc_html_e( 'Track changes', 'woocommerce-appointments' ); ?>:</label>
					<select name="appointment_note" id="appointment_note">
						<option value="private"><?php esc_html_e( 'Private note', 'woocommerce-appointments' ); ?></option>
						<option value="customer"><?php esc_html_e( 'Note to customer', 'woocommerce-appointments' ); ?></option>
					</select>
					<?php echo wc_help_tip( esc_html__( 'When appointment changes, add a private note or send a note to customer', 'woocommerce-appointments' ) ); // WPCS: XSS ok. ?>
				</div>
				<div class="clear"></div>
			</div>
			<div class="major-save-actions">
				<div id="delete-action">
					<a class="submitdelete deletion" href="<?php echo get_delete_post_link( $post->ID ); ?> "><?php esc_html_e( 'Move to Trash', 'woocommerce-appointments' ); ?></a>
				</div>
				<div id="publishing-action">
					<input
						type="submit"
						class="button save_order button-primary tips"
						name="save"
						value="<?php esc_html_e( 'Update', 'woocommerce-appointments' ); ?>"
						data-tip="<?php echo wc_sanitize_tooltip( __( 'Save/update the appointment', 'woocommerce-appointments' ) ); // WPCS: XSS ok. ?>"
					/>
				</div>
				<div class="clear"></div>
			</div>
		</div>
		<?php
	}

	public function track_appointment_changes( $appointment, $data_store, array $get_changes ): void {
		// Don't procede if id is not of a valid appointment.
		if ( ! is_a( $appointment, 'WC_Appointment' ) ) {
			return;
		}

		// Get order object.
		$order = $appointment->get_order();

		// Only add note when: start date, staff, quantity or product changes.
		if ($get_changes && $order && $appointment->is_edited_from_meta_box() && (isset( $get_changes['start'] ) || isset( $get_changes['staff_ids'] ) || isset( $get_changes['qty'] ) || isset( $get_changes['product_id'] ))) {
            // Get note type.
            $note_type = isset( $_POST['appointment_note'] ) && 'customer' === $_POST['appointment_note'] ? 'customer' : 'private';
            // Build detailed change message.
            $change_details = $this->build_change_details( $appointment, $get_changes );
            // Build the notice with change details.
            $notice = sprintf(
					/* translators: %1$d: appointment id, %2$s: change details */
					__( 'Appointment #%1$d has been updated.%2$s', 'woocommerce-appointments' ),
                $appointment->get_id(),
                $change_details,
            );
            // Allow filtering the notice.
            $notice = apply_filters(
                'woocommerce_appointment_edited_notice',
                $notice,
                $appointment,
                $get_changes,
            );
            // Add a note to order privately..
            if ( 'customer' === $note_type ) {
					$order->add_order_note( $notice, true, true );
				// Send the order note to customer.
				} else {
					$order->add_order_note( $notice, false, true );
				}
        }
	}

	/**
	 * Build detailed change message for order notes.
	 *
	 * @param WC_Appointment $appointment Appointment object.
	 * @param array          $get_changes Array of changes.
	 * @return string Change details message.
	 */
	private function build_change_details( \WC_Appointment $appointment, array $get_changes ): string {
		if ( ! $appointment->get_id() ) {
			return '';
		}

		// Get old values from database.
		try {
			$old_appointment = get_wc_appointment( $appointment->get_id() );
			if ( ! $old_appointment ) {
				return '';
			}
		} catch ( Exception $e ) {
			return '';
		}

		$change_parts = [];

		// Time changes.
		if ( isset( $get_changes['start'] ) || isset( $get_changes['end'] ) || isset( $get_changes['all_day'] ) ) {
			$old_range = $this->format_appointment_range(
			    $old_appointment->get_start( 'edit' ),
			    $old_appointment->get_end( 'edit' ),
			    $old_appointment->get_all_day( 'edit' ),
			);
			$new_range = $this->format_appointment_range(
			    $get_changes['start'] ?? $appointment->get_start( 'edit' ),
			    $get_changes['end'] ?? $appointment->get_end( 'edit' ),
			    $get_changes['all_day'] ?? $appointment->get_all_day( 'edit' ),
			);

			if ( $old_range && $new_range && $old_range !== $new_range ) {
				$change_parts[] = sprintf(
					/* translators: %1$s: old range, %2$s: new range */
					__( 'Time: %1$s → %2$s', 'woocommerce-appointments' ),
				    $old_range,
				    $new_range,
				);
			}
		}

		// Staff changes.
		if ( isset( $get_changes['staff_ids'] ) ) {
			$old_names = $this->get_staff_names( $old_appointment->get_staff_ids( 'edit' ) );
			$new_names = $this->get_staff_names( $get_changes['staff_ids'] );
			if ( $old_names !== $new_names ) {
				$change_parts[] = sprintf(
					/* translators: %1$s: old staff, %2$s: new staff */
					__( 'Staff: %1$s → %2$s', 'woocommerce-appointments' ),
				    $old_names ?: __( 'None', 'woocommerce-appointments' ),
				    $new_names ?: __( 'None', 'woocommerce-appointments' ),
				);
			}
		}

		// Quantity changes.
		if ( isset( $get_changes['qty'] ) ) {
			$new_qty = absint( $get_changes['qty'] );
			$old_qty = $old_appointment->get_qty( 'edit' );
			if ( $old_qty != $new_qty ) {
				$change_parts[] = sprintf(
					/* translators: %1$d: old quantity, %2$d: new quantity */
					__( 'Quantity: %1$d → %2$d', 'woocommerce-appointments' ),
				    $old_qty,
				    $new_qty,
				);
			}
		}

		// Product changes.
		if ( isset( $get_changes['product_id'] ) ) {
			$old_id = $old_appointment->get_product_id( 'edit' );
			$new_id = absint( $get_changes['product_id'] );
			if ( $old_id != $new_id ) {
				$old_product = $old_id ? wc_get_product( $old_id ) : null;
				$new_product = $new_id ? wc_get_product( $new_id ) : null;
				$change_parts[] = sprintf(
					/* translators: %1$s: old product, %2$s: new product */
					__( 'Product: %1$s → %2$s', 'woocommerce-appointments' ),
				    $old_product ? $old_product->get_name() : __( 'None', 'woocommerce-appointments' ),
				    $new_product ? $new_product->get_name() : __( 'None', 'woocommerce-appointments' ),
				);
			}
		}

		return [] !== $change_parts ? '<br><br>' . implode( '<br>', $change_parts ) : '';
	}

	/**
	 * Format appointment time range for display.
	 *
	 * @param int  $start    Start timestamp.
	 * @param int  $end      End timestamp.
	 * @param bool $all_day  Whether appointment is all day.
	 * @return string Formatted range.
	 */
	private function format_appointment_range( $start, $end, $all_day ) {
		if ( ! $start || ! $end ) {
			return '';
		}

		$date_format = wc_appointments_date_format();
		$time_format = wc_appointments_time_format();
		$start_date = date_i18n( $date_format, $start );
		$end_date = date_i18n( $date_format, $all_day ? $end - 1 : $end );

		if ( $all_day ) {
			return $start_date === $end_date ? $start_date : $start_date . ' – ' . $end_date;
		}

		$start_time = date_i18n( $time_format, $start );
		$end_time = date_i18n( $time_format, $end );

		if ( $start_date === $end_date ) {
			return $start_date . ', ' . $start_time . ' – ' . $end_time;
		}

		return $start_date . ', ' . $start_time . ' – ' . $end_date . ', ' . $end_time;
	}

	/**
	 * Get staff names from staff IDs.
	 *
	 * @param array|int $staff_ids Staff IDs.
	 * @return string Comma-separated staff names.
	 */
	private function get_staff_names( $staff_ids ): string {
		if ( empty( $staff_ids ) ) {
			return '';
		}

		$names = [];
		foreach ( (array) $staff_ids as $staff_id ) {
			$staff = new WC_Product_Appointment_Staff( $staff_id );
			if ( $staff->get_id() ) {
				$names[] = $staff->get_display_name();
			}
		}

		return implode( ', ', $names );
	}
}

return new WC_Appointment_Meta_Box_Save();
