<?php

namespace Gigneo;

use DateMalformedStringException;
use DateTime;
use DateTimeZone;
use WP_Query;
use WP_Term_Query;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

/**
 * Class Orders
 *
 * Plugin Orders class for Gigneo.
 *
 * @since 0.0.1
 */
class Orders {


	/**
	 * The single instance of the class.
	 *
	 * @var Services|null
	 * @since 0.0.1
	 */
	private static $instance = null;

	/**
	 * Constructor for the class.
	 *
	 * @since 0.0.1
	 */
	public function __construct() {

		// Meta Box.
		add_action( 'add_meta_boxes_gigneo_order', array( $this, 'order_meta_boxes' ) );

		// Remove default publish meta-box.
		add_action( 'admin_menu', array( $this, 'remove_publish_meta_box' ) );

		// Remove Quick Links Edit.
		add_filter( 'post_row_actions', array( $this, 'remove_quick_edit_and_edit_links' ), 10, 2 );

		// Archive columns content.
		add_filter( 'manage_gigneo_order_posts_columns', array( $this, 'custom_order_columns' ) );

		// Archive columns content.
		add_action( 'manage_gigneo_order_posts_custom_column', array( $this, 'order_column_content' ), 10, 2 );

		// Service Order Process.
		add_action( 'wp_ajax_gigneo_service_order_process', array( $this, 'service_order_process' ) );
		add_action( 'wp_ajax_nopriv_gigneo_service_order_process', array( $this, 'service_order_process' ) );

		// Service Order Summary.
		add_action( 'wp_ajax_gigneo_service_order_summary', array( $this, 'service_order_summary' ) );
		add_action( 'wp_ajax_nopriv_gigneo_service_order_summary', array( $this, 'service_order_summary' ) );

		// Service Place Order.
		add_action( 'wp_ajax_gigneo_service_order_place', array( $this, 'service_order_place' ) );

		// Job Place Order.
		add_action( 'wp_ajax_gigneo_job_order_place', array( $this, 'job_order_place' ) );

		// Submit Job Proposal.
		add_action( 'wp_ajax_gigneo_submit_job_proposal', array( $this, 'submit_job_proposal' ) );

		// Withdraw Proposal.
		add_action( 'wp_ajax_gigneo_withdraw_proposal', array( $this, 'withdraw_proposal' ) );

		// Short list Proposal.
		add_action( 'wp_ajax_gigneo_shortlist_proposal', array( $this, 'shortlist_proposal' ) );

		// Service Order Deliver.
		add_action( 'wp_ajax_gigneo_service_order_deliver', array( $this, 'service_order_deliver' ) );

		// Service Order Revision.
		add_action( 'wp_ajax_gigneo_service_order_revision', array( $this, 'service_order_revision' ) );

		// Service Order Delivery Approve.
		add_action( 'wp_ajax_gigneo_service_order_delivery_approve', array( $this, 'service_order_delivery_approve' ) );

		// Service Order Complete.
		add_action( 'wp_ajax_gigneo_service_order_complete', array( $this, 'service_order_complete' ) );

		// Service Order Cancel.
		add_action( 'wp_ajax_gigneo_service_order_cancel', array( $this, 'service_order_cancel' ) );

		// Service Order Cancel Respond.
		add_action( 'wp_ajax_gigneo_service_order_cancel_respond', array( $this, 'service_order_cancel_respond' ) );

		// Service Review.
		add_action( 'wp_ajax_gigneo_service_order_review', array( $this, 'service_order_review' ) );

		// Send Message.
		add_action( 'wp_ajax_gigneo_send_order_message', array( $this, 'send_message' ) );

		// Get Messages.
		add_action( 'wp_ajax_gigneo_get_order_message', array( $this, 'get_order_messages' ) );

		// Upload Service Order Attachments.
		add_action( 'wp_ajax_gigneo_service_order_attachments_upload', array( $this, 'service_order_attachments_upload' ) );

		// Job Order Deliver.
		add_action( 'wp_ajax_gigneo_job_order_deliver', array( $this, 'job_order_deliver' ) );

		// Job Order Revision.
		add_action( 'wp_ajax_gigneo_job_order_revision', array( $this, 'job_order_revision' ) );

		// Job Order Delivery Approve.
		add_action( 'wp_ajax_gigneo_job_order_delivery_approve', array( $this, 'job_order_delivery_approve' ) );

		// Job Order Complete.
		add_action( 'wp_ajax_gigneo_job_order_complete', array( $this, 'job_order_complete' ) );

		// Job Order Cancel.
		add_action( 'wp_ajax_gigneo_job_order_cancel', array( $this, 'job_order_cancel' ) );

		// Job Order Cancel Respond.
		add_action( 'wp_ajax_gigneo_job_order_cancel_respond', array( $this, 'job_order_cancel_respond' ) );

		// Job Order Review.
		add_action( 'wp_ajax_gigneo_job_order_review', array( $this, 'job_order_review' ) );

		// Upload Job Order Attachments.
		add_action( 'wp_ajax_gigneo_job_order_attachments_upload', array( $this, 'job_order_attachments_upload' ) );

		// Ajax function for handling notes.
		add_action( 'wp_ajax_gigneo_order_private_note', array( $this, 'order_private_note' ) );

		// Delete note comment.
		add_action( 'wp_ajax_gigneo_delete_private_note', array( $this, 'delete_private_note' ) );

		// Moderator Cancel the Service Order.
		add_action( 'wp_ajax_gigneo_service_order_cancel_by_moderator', array( $this, 'service_order_cancel_by_moderator' ) );

		// Moderator Complete the Service Order.
		add_action( 'wp_ajax_gigneo_service_order_complete_by_moderator', array( $this, 'service_order_complete_by_moderator' ) );

		// Moderator Cancel the Job Order.
		add_action( 'wp_ajax_gigneo_job_order_cancel_by_moderator', array( $this, 'job_order_cancel_by_moderator' ) );

		// Moderator Complete the Job Order.
		add_action( 'wp_ajax_gigneo_job_order_complete_by_moderator', array( $this, 'job_order_complete_by_moderator' ) );

		// Service Order Invoice Download.
		add_action( 'wp_ajax_gigneo_order_invoice_download', array( $this, 'order_invoice_download' ) );

		// Moderator Service Order Invoice Download.
		add_action( 'wp_ajax_gigneo_moderator_invoice_download', callback: array( $this, 'moderator_invoice_download' ) );

		// Menu counter.
		add_action( 'admin_menu', array( $this, 'menu_counter' ) );

		// Count.
		add_action( 'get_order_type_count', array( $this, 'get_order_type_count' ) );
	}


	/**
	 * Ensures only one instance of the class is loaded or can be loaded.
	 *
	 * @return Services
	 * @since 0.0.1
	 */
	public static function instance() {
		if ( is_null( self::$instance ) ) {
			self::$instance = new self();
		}

		return self::$instance;
	}

	/**
	 * Custom column content.
	 */
	public static function order_column_content( $column, $post_id ) {
		$post                    = get_post( $post_id );
		$date_format             = get_option( 'date_format' );
		$post_status             = $post->post_status;
		$post_status             = 'publish' === $post_status ? 'active' : $post_status;
		$freelancer_profile_id   = gigneo_post_meta( $post_id, '_gigneo_pm_freelancer_profile_id' );
		$freelancer_profile_name = gigneo_profile_name( $freelancer_profile_id );
		$employer_profile_id     = gigneo_post_meta( $post_id, '_gigneo_pm_profile_id' );
		$employer_profile_name   = gigneo_profile_name( $employer_profile_id );
		$status_tag              = gigneo_status_class( $post_status );
		$status_label            = gigneo_status_label( $post_status );
		$total                   = gigneo_post_meta( $post_id, '_gigneo_pm_subtotal' );
		$due_date_raw            = gigneo_post_meta( $post_id, '_gigneo_pm_delivery_at_gmt' );
		$due_date                = ! empty( $due_date_raw ) ? gigneo_datetime( $due_date_raw, $date_format ) : esc_html__( 'None', 'gigneo' );
		$gn_type                 = gigneo_post_meta( $post_id, '_gigneo_pm_delivery_type' );
		$order_type              = gigneo_post_meta( $post_id, '_gigneo_pm_order_type' );

		$freelancer_profile_url = add_query_arg(
			array(
				's'         => $freelancer_profile_id,
				'post_type' => 'gigneo_freelancer',
			),
			admin_url( 'edit.php' )
		);
		$employer_profile_url   = add_query_arg(
			array(
				's'         => $employer_profile_id,
				'post_type' => 'gigneo_employer',
			),
			admin_url( 'edit.php' )
		);

		switch ( $column ) {
			case 'status':
				echo '<span class="gn-status-tag ' . esc_attr( $status_tag ) . '"> ' . esc_html( $status_label ) . '</span>';
				break;
			case 'freelancer':
				echo '<a class="gn-user-url" href="' . esc_url( $freelancer_profile_url ) . '" target="_blank">' . esc_html( $freelancer_profile_name ) . '</a>';
				break;
			case 'employer':
				echo '<a class="gn-user-url" href="' . esc_url( $employer_profile_url ) . '" target="_blank">' . esc_html( $employer_profile_name ) . '</a>';
				break;
			case 'order_no':
				echo esc_html( gigneo_format_id( $post_id, 'order', false ) );
				break;
			case 'totals':
				echo esc_html( gigneo_price_format( $total, true ) );
				break;
			case 'due_date':
				echo esc_html( $due_date );
				break;
			case 'type':
				echo esc_html( sprintf( '%s — %s', ucfirst( $order_type ), ucfirst( $gn_type ) ) );
				break;
		}
	}

	public static function get_order_type_count( $profile_id, $type = 'service', $status = '' ) {

		$valid_types = array( 'service', 'job' );

		if ( $type && ! in_array( $type, $valid_types, true ) ) {
			return 0;
		}

		if ( ! empty( $status ) ) {
			$status = array( 'publish', 'delayed', 'delivered', 'completed', 'cancelled', 'disputed' );
		}

		if ( is_string( $status ) ) {
			$status = array( $status );
		}

		$profile_type = gigneo_profile_type( $profile_id );
		$meta_key     = 'freelancer' === $profile_type ? '_gigneo_pm_freelancer_profile_id' : '_gigneo_pm_profile_id';

		$args = array(
			'post_type'      => 'gigneo_order',
			'post_status'    => $status,
			'posts_per_page' => -1,
			'fields'         => 'ids',
			'no_found_rows'  => false,
			// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
			'meta_query'     => array(
				array(
					'key'     => $meta_key,
					'value'   => $profile_id,
					'compare' => '=',
				),
				array(
					'key'     => '_gigneo_pm_order_type',
					'value'   => $type,
					'compare' => '=',
				),
			),
			// phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
		);

		$query = new WP_Query( $args );

		return apply_filters( 'gigneo_order_type_count', $query->found_posts, $profile_id, $type );
	}

	/**
	 * Handle pending reports counter in admin menu.
	 *
	 * Adds a badge to the "Orders" menu item showing count of reports under review.
	 *
	 * @return void
	 */
	public function menu_counter() {

		global $submenu;
		$counts  = wp_count_posts( 'gigneo_order' );
		$pending = isset( $counts->{'disputed'} ) ? (int) $counts->{'disputed'} : 0;

		if ( $pending <= 0 || ! isset( $submenu['gigneo'] ) ) {
			return;
		}

		foreach ( $submenu['gigneo'] as $key => $item ) {
			if ( isset( $item[2] ) && 'edit.php?post_type=gigneo_order' === $item[2] ) {
				// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
				$submenu['gigneo'][ $key ][0] .= sprintf(
					' <span class="update-plugins count-%1$d"><span class="plugin-count">%1$d</span></span>',
					$pending
				);
				break;
			}
		}
	}

	/**
	 * Add custom meta boxes to the gigneo_order edit screen.
	 */
	public function order_meta_boxes() {

		add_meta_box(
			'gigneo_order_main',
			esc_html__( 'Order details', 'gigneo' ),
			array( $this, 'render_order_main_meta_box' ),
			'gigneo_order',
			'advanced',
			'high'
		);

		add_meta_box(
			'gigneo_delivery_info',
			esc_html__( 'Time until delivery', 'gigneo' ),
			array( $this, 'render_delivery_info_meta_box' ),
			'gigneo_order',
			'side',
			'high'
		);
		add_meta_box(
			'gigneo_tracking_info',
			esc_html__( 'Order tracking', 'gigneo' ),
			array( $this, 'render_tracking_info_meta_box' ),
			'gigneo_order',
			'side',
			'high'
		);
		add_meta_box(
			'gigneo_revisions',
			esc_html__( 'Revisions', 'gigneo' ),
			array( $this, 'render_revisions_meta_box' ),
			'gigneo_order',
			'side',
			'high'
		);
		add_meta_box(
			'gigneo_freelancer',
			esc_html__( 'Freelancer', 'gigneo' ),
			array( $this, 'render_freelancer_meta_box' ),
			'gigneo_order',
			'side',
			'high'
		);
		add_meta_box(
			'gigneo_employer',
			esc_html__( 'Employer', 'gigneo' ),
			array( $this, 'render_employer_meta_box' ),
			'gigneo_order',
			'side',
			'high'
		);
		do_action( 'gigneo_order_meta_boxes_after', $this );

		// Remove default editor.
		remove_post_type_support( 'gigneo_order', 'editor' );

		// Remove default title.
		remove_post_type_support( 'gigneo_order', 'title' );

		// Remove default Comments.
		remove_post_type_support( 'gigneo_order', 'comments' );
	}

	/**
	 * Remove the default publish meta box from the gigneo_order edit screen.
	 *
	 * @return void
	 */
	public function remove_publish_meta_box() {
		remove_meta_box( 'submitdiv', 'gigneo_order', 'side' );
	}

	/**
	 * Render the revisions meta box.
	 */
	public function render_revisions_meta_box() {
		global $typenow, $post;
		if ( 'gigneo_order' === $typenow ) {
			gigneo_load_template( 'admin/parts/order/service/revisions.php', false, array( 'p_id' => $post->ID ) );
		}
	}

	/**
	 * Render the freelancer meta box.
	 */
	public function render_freelancer_meta_box() {
		global $typenow, $post;
		if ( 'gigneo_order' === $typenow ) {
			$order_type = gigneo_post_meta( $post->ID, '_gigneo_pm_order_type' );
			$order_type = ! empty( $order_type ) && 'job' === $order_type ? 'job' : 'service';
			gigneo_load_template( "admin/parts/order/$order_type/freelancer.php", false, array( 'p_id' => $post->ID ) );
		}
	}

	/**
	 * Render the employer meta box.
	 */
	public function render_employer_meta_box() {
		global $typenow, $post;
		if ( 'gigneo_order' === $typenow ) {
			$order_type = gigneo_post_meta( $post->ID, '_gigneo_pm_order_type' );
			$order_type = ! empty( $order_type ) && 'job' === $order_type ? 'job' : 'service';
			gigneo_load_template( "admin/parts/order/$order_type/employer.php", false, array( 'p_id' => $post->ID ) );
		}
	}

	/**
	 * Loads the order template in the admin dashboard.
	 */
	public function render_order_main_meta_box() {
		global $typenow;
		if ( 'gigneo_order' === $typenow ) {
			gigneo_load_template( 'admin/order.php' );
		}
	}

	/**
	 * Loads the order template in the admin dashboard.
	 */
	public function render_delivery_info_meta_box() {
		global $typenow, $post;
		if ( 'gigneo_order' === $typenow ) {
			$order_type = gigneo_post_meta( $post->ID, '_gigneo_pm_order_type' );
			$order_type = ! empty( $order_type ) && 'job' === $order_type ? 'job' : 'service';
			gigneo_load_template( "admin/parts/order/$order_type/delivery.php", false, array( 'p_id' => $post->ID ) );
		}
	}

	/**
	 * Remove the default quick edit on gigneo_order.
	 */
	public function remove_quick_edit_and_edit_links( $actions, $post ) {
		if ( 'gigneo_order' === $post->post_type ) {
			unset( $actions['edit'] );
			unset( $actions['inline hide-if-no-js'] );
		}
		return $actions;
	}

	/**
	 * Loads the order template in the admin dashboard.
	 *
	 */
	public function render_tracking_info_meta_box() {
		global $typenow, $post;
		if ( 'gigneo_order' === $typenow ) {
			$order_type = gigneo_post_meta( $post->ID, '_gigneo_pm_order_type' );
			$order_type = ! empty( $order_type ) && 'job' === $order_type ? 'job' : 'service';
			gigneo_load_template( "admin/parts/order/$order_type/tracking.php", false, array( 'p_id' => $post->ID ) );
		}
	}

	/**
	 * Custom columns for the gigneo_order list table.
	 */
	public function custom_order_columns( $columns ) {
		$columns = array(
			'cb'         => $columns['cb'],
			'title'      => __( 'Title', 'gigneo' ),
			'order_no'   => __( 'Order No', 'gigneo' ),
			'type'       => __( 'Type', 'gigneo' ),
			'employer'   => __( 'Employer', 'gigneo' ),
			'freelancer' => __( 'Freelancer', 'gigneo' ),
			'due_date'   => __( 'Due Date', 'gigneo' ),
			'status'     => __( 'Status', 'gigneo' ),
			'totals'     => __( 'Order Amount', 'gigneo' ),
		);
		return $columns;
	}

	/**
	 * Submit Job Proposal
	 *
	 * @since 0.0.1
	 */
	public function submit_job_proposal() {
		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$current_user = wp_get_current_user();
		$profile_id   = gigneo_active_profile_id( $current_user->ID );
		$profile_type = gigneo_profile_type( $profile_id );

		if ( ! is_user_logged_in() || empty( $current_user->ID ) || empty( $profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$job_id = isset( $_POST['job_id'] ) ? absint( wp_unslash( $_POST['job_id'] ) ) : 0;
		if ( empty( $job_id ) && isset( $_POST['p_id'] ) ) {
			$job_id = absint( wp_unslash( $_POST['p_id'] ) );
		}

		$job_post_type = get_post_type( $job_id );

		if ( 'freelancer' !== $profile_type || empty( $job_id ) || 'gigneo_job' !== $job_post_type ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
		}

		$job_status = get_post_status( $job_id );
		if ( 'publish' !== $job_status ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You cannot submit a proposal to an inactive job.', 'gigneo' ) ), 400 );
		}

		$this->validate_proposal_submission_conditions( $profile_id, $current_user->ID );

		$apply_type = gigneo_option( '_gigneo_ts_job_apply_type', 'free' );
		if ( 'paid' === $apply_type ) {
			$available_credits = gigneo_available_credits( $profile_id );
			$required_credits  = gigneo_option( '_gigneo_ts_job_apply_credits', 0 );
			if ( $available_credits < $required_credits ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Insufficient credit balance.', 'gigneo' ) ), 400 );
			}
		}

		$existing_proposals = get_posts(
			array(
				'post_type'      => 'gigneo_proposal',
				'posts_per_page' => 1,
				'post_status'    => 'any',
				// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
				'meta_query'     => array(
					'relation' => 'AND',
					array(
						'key'   => '_gigneo_pm_job_id',
						'value' => $job_id,
					),
					array(
						'key'   => '_gigneo_pm_profile_id',
						'value' => $profile_id,
					),
				),
				// phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
			)
		);

		$is_update   = false;
		$proposal_id = 0;
		if ( ! empty( $existing_proposals ) ) {
			$existing_proposal = $existing_proposals[0];
			if ( 'publish' === $existing_proposal->post_status ) {
				$proposal_id = $existing_proposal->ID;
				$is_update   = true;
			} else {
				wp_send_json_error( array( 'message' => esc_html__( 'You already submitted a proposal for this job.', 'gigneo' ) ), 400 );
			}
		}

		$bid_amount      = isset( $_POST['bid_amount'] ) ? sanitize_text_field( wp_unslash( $_POST['bid_amount'] ) ) : '';
		$duration        = isset( $_POST['duration'] ) ? sanitize_text_field( wp_unslash( $_POST['duration'] ) ) : '';
		$duration_length = isset( $_POST['duration_length'] ) ? sanitize_text_field( wp_unslash( $_POST['duration_length'] ) ) : '';
		$cover_letter    = isset( $_POST['cover_letter'] ) ? wp_kses_post( wp_unslash( $_POST['cover_letter'] ) ) : '';

		if ( empty( $bid_amount ) || empty( $duration ) || empty( $duration_length ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Please fill all the required fields.', 'gigneo' ) ), 400 );
		}

		// Insert proposal post.
		$proposal_args = array(
			'post_type'   => 'gigneo_proposal',
			'post_status' => 'publish',
			'post_author' => $current_user->ID,
			'post_title'  => sprintf(
			// translators: %s is the job ID.
				esc_html__( 'Proposal for job #%s', 'gigneo' ),
				gigneo_format_id( $job_id, 'job', false )
			),
		);

		if ( ! empty( $proposal_id ) ) {
			$proposal_args['ID'] = $proposal_id;
		}

		$proposal_id = wp_insert_post( $proposal_args );

		if ( is_wp_error( $proposal_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Failed to submit proposal.', 'gigneo' ) ), 400 );
		}

		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Files array metadata is validated and handled during upload.
		$proposal_attachments    = isset( $_FILES['proposal_attachments'] ) ? $_FILES['proposal_attachments'] : array();
		$proposal_attachment_ids = array();
		$files_collection        = array();

		if ( ! empty( $proposal_attachments['name'] ) ) {
			$job_order_extensions = (array) gigneo_option( '_gigneo_ts_job_order_extensions', array( 'jpg', 'jpeg', 'png', 'doc' ) );
			$job_order_max_files  = absint( gigneo_option( '_gigneo_ts_job_order_max_files', 3 ) );
			$job_order_max_size   = absint( gigneo_option( '_gigneo_ts_job_order_max_size', 1 ) );
			$max_file_size        = $job_order_max_size * 1024 * 1024;
			$total_files          = count( $proposal_attachments['name'] );

			if ( $total_files > $job_order_max_files ) {
				wp_send_json_error(
					array(
						'message' => sprintf(
						// translators: %s: Maximum number of files allowed.
							esc_html__( 'You can only upload up to %s files.', 'gigneo' ),
							$job_order_max_files
						),
					),
					403
				);
			}

			for ( $i = 0; $i < $total_files; $i++ ) {

				$name = isset( $proposal_attachments['name'][ $i ] )
					? sanitize_file_name( wp_unslash( $proposal_attachments['name'][ $i ] ) )
					: '';

				$tmp_name = isset( $proposal_attachments['tmp_name'][ $i ] )
					? sanitize_text_field( $proposal_attachments['tmp_name'][ $i ] )
					: '';

				$size = isset( $proposal_attachments['size'][ $i ] )
					? absint( $proposal_attachments['size'][ $i ] )
					: 0;

				$error = isset( $proposal_attachments['error'][ $i ] )
					? absint( $proposal_attachments['error'][ $i ] )
					: UPLOAD_ERR_NO_FILE;

				$ext = strtolower( pathinfo( $name, PATHINFO_EXTENSION ) );

				if ( UPLOAD_ERR_OK !== $error ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: %s: File name. */
								esc_html__( 'Error uploading file: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				if ( empty( $tmp_name ) || ! is_uploaded_file( $tmp_name ) ) {
					wp_send_json_error(
						array(
							'message' => esc_html__( 'Invalid uploaded file.', 'gigneo' ),
						),
						403
					);
				}

				if ( ! in_array( $ext, $job_order_extensions, true ) ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: %s: File name. */
								esc_html__( 'File type not allowed: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				$filetype = wp_check_filetype_and_ext( $tmp_name, $name );

				if ( empty( $filetype['ext'] ) || empty( $filetype['type'] ) || $filetype['ext'] !== $ext ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: %s: File name. */
								esc_html__( 'File type not allowed: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				if ( $size > $max_file_size ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: 1: File name, 2: Maximum file size in MB. */
								esc_html__( '%1$s exceeds the maximum file size of %2$s MB.', 'gigneo' ),
								esc_html( $name ),
								esc_html( $job_order_max_size )
							),
						),
						403
					);
				}

				$files_collection[] = array(
					'name'     => $name,
					'tmp_name' => $tmp_name,
					'size'     => $size,
					'ext'      => sanitize_key( $ext ),
				);
			}
		}

		if ( ! empty( $files_collection ) ) {
			global $wpdb;
			$table_attachments = $wpdb->prefix . 'gigneo_attachments';
			$proposal_dir      = gigneo_upload_dir_path( '/' . gmdate( 'Y' ) . '/' . gmdate( 'm' ) . '/proposal/' . $proposal_id );

			if ( ! file_exists( $proposal_dir ) ) {
				wp_mkdir_p( $proposal_dir );
			}

			foreach ( $files_collection as $file ) {
				$name     = $file['name'];
				$tmp_name = $file['tmp_name'];
				$size     = $file['size'];
				$file_ext = $file['ext'];

				$random_number = wp_rand( 1000, 9999 );

				$base_name = sanitize_file_name( pathinfo( $name, PATHINFO_FILENAME ) );

				$file_name = $base_name . '-' . $random_number . '.' . $file_ext;

				$file_name = wp_unique_filename( $proposal_dir, $file_name );

				$file_path = trailingslashit( $proposal_dir ) . $file_name;

				if ( is_uploaded_file( $tmp_name ) && gigneo_move_uploaded_file( $tmp_name, $file_path ) ) {

					$path = str_replace( gigneo_upload_dir_path(), '', $file_path );
					$url  = str_replace( gigneo_upload_dir_path(), gigneo_upload_dir_url(), $file_path );

					$meta = array(
						'type' => 'proposal_attachment',
					);

					// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
					$wpdb->insert(
						$table_attachments,
						array(
							'post_id'         => absint( $proposal_id ),
							'profile_id'      => absint( $profile_id ),
							'name'            => $name,
							'mime_type'       => $file_ext,
							'size'            => $size,
							'guid'            => esc_url_raw( $url ),
							'path'            => sanitize_text_field( $path ),
							'post_type'       => 'proposal',
							'meta'            => maybe_serialize( $meta ),
							'uploaded_at'     => current_time( 'mysql' ),
							'uploaded_at_gmt' => current_time( 'mysql', true ),
						),
						array( '%d', '%d', '%s', '%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s' )
					);
					// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

					$proposal_attachment_ids[] = absint( $wpdb->insert_id );
				}
			}
		}

		$submission_type    = gigneo_option( '_gigneo_ts_job_apply_type' );
		$submission_credits = gigneo_option( '_gigneo_ts_job_apply_credits' );
		$available_credits  = gigneo_available_credits( $profile_id );

		if ( 'paid' === $submission_type && ! $is_update ) {

			if ( $available_credits < $submission_credits ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Insufficient credit balance.', 'gigneo' ) ), 403 );
			}

			$invoice_id = wp_insert_post(
				array(
					'post_type'   => 'gigneo_invoice',
					'post_status' => 'paid',
					'post_title'  => sprintf(
					// translators: %s is job ID
						esc_html__( 'Credits Spent — %s', 'gigneo' ),
						esc_html( gigneo_format_id( $proposal_id, 'proposal', false ) )
					),
					'post_author' => $current_user->ID,
				)
			);

			if ( is_wp_error( $invoice_id ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
			}

			update_post_meta( $invoice_id, '_gigneo_pm_profile_id', $profile_id );
			update_post_meta( $invoice_id, '_gigneo_pm_credit', -intval( $submission_credits ) );
			update_post_meta( $invoice_id, '_gigneo_pm_type', 'proposal_submission' );
		}

		// Fetch job author's profile id.
		$job_author          = get_post_field( 'post_author', $job_id );
		$employer_profile_id = gigneo_profile_id( $job_author, 'employer' );

		// Save metas.
		update_post_meta( $proposal_id, '_gigneo_pm_job_id', $job_id );
		update_post_meta( $proposal_id, '_gigneo_pm_profile_id', $profile_id );
		update_post_meta( $proposal_id, '_gigneo_pm_employer_profile_id', $employer_profile_id );
		update_post_meta( $proposal_id, '_gigneo_pm_budget', floatval( $bid_amount ) );
		update_post_meta( $proposal_id, '_gigneo_pm_duration_type', $duration );
		update_post_meta( $proposal_id, '_gigneo_pm_duration', $duration_length );
		update_post_meta( $proposal_id, '_gigneo_pm_cover_letter', $cover_letter );
		update_post_meta( $proposal_id, '_gigneo_pm_attachments', $proposal_attachment_ids );

		// job info.
		$job_price              = gigneo_post_meta( $job_id, '_gigneo_pm_price' );
		$job_type               = gigneo_post_meta( $job_id, '_gigneo_pm_type' );
		$job_delivery_time      = gigneo_post_meta( $job_id, '_gigneo_pm_duration' );
		$job_delivery_time_type = gigneo_post_meta( $job_id, '_gigneo_pm_duration_type' );
		$job_info               = array(
			'job_title'              => get_the_title( $job_id ),
			'job_id'                 => $job_id,
			'job_type'               => $job_type,
			'job_budget'             => $job_price,
			'job_delivery_time'      => $job_delivery_time,
			'job_delivery_time_type' => $job_delivery_time_type,
		);

		// Save job info.
		update_post_meta( $proposal_id, '_gigneo_pm_job_info', $job_info );

		// save intake form data
		do_action( 'gigneo_after_proposal_save', $proposal_id, $job_id );

		$dashboard_page_id = gigneo_option( '_gigneo_ts_template_dashboard' );
		$redirect_url      = ! empty( $dashboard_page_id ) ? get_permalink( $dashboard_page_id ) : home_url();
		$order_data        = array(
			'source' => 'proposal',
			'ref'    => 'list',
		);

		$redirect_url = add_query_arg(
			$order_data,
			$redirect_url
		);

		$response = array(
			'redirect' => esc_url_raw( $redirect_url ),
			'message'  => $is_update ? esc_html__( 'Proposal updated successfully.', 'gigneo' ) : esc_html__( 'Proposal submitted successfully.', 'gigneo' ),
		);

		// Proposal Notification (Onsite).
		$proposal_nf_title = $is_update ? esc_html__( 'Proposal updated', 'gigneo' ) : esc_html__( 'New proposal', 'gigneo' );
		if ( $is_update ) {
			$proposal_nf_content = sprintf(
			// translators: 1: Freelancer Name, 2: Job Title
				esc_html__( 'Hey %1$s, %2$s has updated their proposal on your job "%3$s".', 'gigneo' ),
				gigneo_profile_name( $employer_profile_id ),
				gigneo_profile_name( $profile_id ),
				get_the_title( $job_id )
			);
		} else {
			$proposal_nf_content = sprintf(
			// translators: 1: Freelancer Name, 2: Job Title
				esc_html__( 'Hey %1$s, You have received a new proposal from %2$s on your job "%3$s".', 'gigneo' ),
				gigneo_profile_name( $employer_profile_id ),
				gigneo_profile_name( $profile_id ),
				get_the_title( $job_id )
			);
		}
		Notifications::trigger_notification( $proposal_nf_title, $proposal_nf_content, 'proposal', 'proposal_submitted', $employer_profile_id, $proposal_id );

		// Proposal Email (WooCommerce).
		$wc_mailer = WC()->mailer();
		$wc_emails = $wc_mailer->get_emails();
		if ( isset( $wc_emails['Gigneo_Job_Proposal_Submitted'] ) ) {
			$wc_emails['Gigneo_Job_Proposal_Submitted']->trigger( $employer_profile_id, $proposal_id );
		}

		wp_send_json_success( $response, 200 );
	}

	public function validate_proposal_submission_conditions( $profile_id, $user_id ) {

		if ( empty( $profile_id ) || empty( $user_id ) ) {
			return;
		}

		$submission_conditions = gigneo_option( '_gigneo_ts_job_apply_conditions' );

		if ( empty( $submission_conditions ) && ! is_array( $submission_conditions ) ) {
			return;
		}

		// Profile Image.
		if ( in_array( 'profile_image', $submission_conditions, true ) ) {
			if ( empty( get_post_thumbnail_id( $profile_id ) ) ) {
				wp_send_json_error(
					array( 'message' => esc_html__( 'To continue, please upload a profile image first', 'gigneo' ) ),
					403
				);
			}
		}

		// Email Verification.
		if ( in_array( 'email_verified', $submission_conditions, true ) && in_array( 'email_verification', gigneo_active_modules(), true ) ) {
			$email_verified = get_user_meta( $user_id, '_gigneo_um_email_verified', true );

			if ( empty( $email_verified ) ) {
				wp_send_json_error(
					array( 'message' => esc_html__( 'Please verify your email address before proposal submission.', 'gigneo' ) ),
					403
				);
			}
		}
	}

	public function service_order_process() {
		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$service_id = isset( $_POST['service_id'] ) ? absint( $_POST['service_id'] ) : 0;
		$plan       = isset( $_POST['plan'] ) ? sanitize_text_field( wp_unslash( $_POST['plan'] ) ) : '';
		$status     = ! empty( $service_id ) ? get_post_status( $service_id ) : '';

		if ( empty( $service_id ) || empty( $plan ) || 'publish' !== $status ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
		}

		$service_plan = gigneo_post_meta( $service_id, "_gigneo_pm_price_plan_{$plan}_price" );

		if ( ! in_array( $plan, array( 'one', 'two', 'three' ), true ) || empty( $service_plan ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
		}

		$service_type = gigneo_post_meta( $service_id, '_gigneo_pm_type' );
		$service_type = ! empty( $service_type ) ? $service_type : '';

		$order_data = array(
			'source' => 'service',
			'p_id'   => $service_id,
			'plan'   => $plan,
		);

		if ( 'onsite' === $service_type ) {
			$service_delivery_type = gigneo_post_meta( $service_id, '_gigneo_pm_delivery_type' );
			$delivery_type         = isset( $_POST['delivery_type'] ) ? sanitize_text_field( wp_unslash( $_POST['delivery_type'] ) ) : '';
			if ( 'both' !== $service_delivery_type ) {
				$delivery_type = $service_delivery_type;
			}

			$location_key = isset( $_POST['location_key'] ) ? absint( $_POST['location_key'] ) : 0;
			$location     = ( isset( $_POST['location'][ $location_key ] ) && is_array( $_POST['location'][ $location_key ] ) )
				? array_map( 'sanitize_text_field', (array) wp_unslash( $_POST['location'][ $location_key ] ) )
				: array();

			if ( empty( $delivery_type ) || ! isset( $location_key ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Required field could not be empty.', 'gigneo' ) ), 400 );
			}
			if ( 'visit_me' !== $service_delivery_type && empty( $location ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Required field could not be empty.', 'gigneo' ) ), 400 );
			}
			if ( ! in_array( $delivery_type, array( 'visit_me', 'on_location' ), true ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Invalid delivery type.', 'gigneo' ) ), 400 );
			}

			$order_data['delivery_type'] = $delivery_type;
			$order_data['location_id']   = $location_key;

			if ( 'on_location' === $delivery_type ) {
				$friendly_address = ! empty( $location['address'] ) ? sanitize_text_field( $location['address'] ) : '';

				if ( empty( $friendly_address ) ) {
					wp_send_json_error( array( 'message' => esc_html__( 'Required field could not be empty.', 'gigneo' ) ), 400 );
				}

				$order_data['friendly_address'] = $friendly_address;

				$map_location = ! empty( $location['map_location'] ) ? sanitize_text_field( $location['map_location'] ) : '';
				$map_location = ! empty( $map_location ) ? json_decode( stripslashes( $map_location ), true ) : array();

				$order_data = apply_filters( 'gigneo_service_order_process_before', $order_data, $map_location );
			}
		}

		$hire_template = gigneo_option( '_gigneo_ts_template_hire' );

		if ( empty( $hire_template ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Hire page is not configured.', 'gigneo' ) ), 400 );
		}

		$redirect_url = add_query_arg(
			$order_data,
			get_permalink( $hire_template )
		);

		$response = array(
			'redirect_url' => esc_url_raw( $redirect_url ),
			'message'      => esc_html__( 'Redirecting to hiring page...', 'gigneo' ),
		);

		wp_send_json_success( $response, 200 );
	}

	/**
	 * Generate service order summary via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function service_order_summary() {

		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$service_id = isset( $_POST['p_id'] ) ? absint( $_POST['p_id'] ) : 0;

		if ( empty( $service_id ) || get_post_type( $service_id ) !== 'gigneo_service' || 'publish' !== get_post_status( $service_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
		}

		$plan          = isset( $_POST['plan'] ) ? sanitize_text_field( wp_unslash( $_POST['plan'] ) ) : '';
		$delivery_type = isset( $_POST['delivery_type'] ) ? sanitize_text_field( wp_unslash( $_POST['delivery_type'] ) ) : '';
		$location      = ( isset( $_POST['location'] ) && is_array( $_POST['location'] ) ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['location'] ) ) : array();
		$location_id   = isset( $_POST['location_id'] ) ? sanitize_text_field( wp_unslash( $_POST['location_id'] ) ) : '';
		$address       = isset( $_POST['friendly_address'] ) ? sanitize_text_field( wp_unslash( $_POST['friendly_address'] ) ) : '';

		$addons = array();

		if ( isset( $_POST['addons'] ) && is_array( $_POST['addons'] ) ) {
			$addons_post = map_deep( wp_unslash( $_POST['addons'] ), 'absint' );
			foreach ( $addons_post as $addon ) {
				if ( ! isset( $addon['id'] ) ) {
					continue;
				}
				$addons[] = array(
					'id'       => $addon['id'],
					'quantity' => $addon['quantity'] ?? 1,
				);
			}
		}

		if ( ! in_array( $plan, array( 'one', 'two', 'three' ), true ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Invalid service price plan.', 'gigneo' ) ), 400 );
		}

		$service_type = gigneo_post_meta( $service_id, '_gigneo_pm_type' );
		$service_type = ! empty( $service_type ) ? $service_type : '';

		if ( 'onsite' === $service_type ) {
			if ( ! in_array( $delivery_type, array( 'visit_me', 'on_location' ), true ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Invalid delivery type.', 'gigneo' ) ), 400 );
			}
		}

		$template_args = array(
			'service_id'    => $service_id,
			'plan'          => $plan,
			'delivery_type' => $delivery_type,
			'location_id'   => $location_id,
			'location'      => $location,
			'address'       => $address,
			'addons'        => $addons,
		);

		ob_start();
		gigneo_load_template( 'single/hire/service/parts/summary', true, $template_args );
		$summary             = ob_get_clean();
		$response['summary'] = $summary;
		$response['addons']  = $addons;

		wp_send_json_success( $response );
	}

	/**
	 * Place service order via AJAX.
	 *
	 * @throws DateMalformedStringException
	 * @since 0.0.1
	 */
	public function service_order_place() {

		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$profile_id   = gigneo_active_profile_id();
		$current_user = wp_get_current_user();

		if ( ! is_user_logged_in() ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Please log in to perform this action.', 'gigneo' ) ), 403 );
			exit;
		}

		if ( empty( $current_user->ID ) || empty( $profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$profile_type = gigneo_profile_type( $profile_id );

		if ( 'employer' !== $profile_type ) {
			wp_send_json_error(
				array(
					'message' => sprintf(
					// translators: %s is employer singular
						esc_html__( '%s can only hire service.', 'gigneo' ),
						gigneo_employer_singular()
					),
				),
				403
			);
		}

		$service_id   = isset( $_POST['p_id'] ) ? absint( $_POST['p_id'] ) : 0;
		$service_plan = isset( $_POST['plan'] ) ? sanitize_text_field( wp_unslash( $_POST['plan'] ) ) : 'one';

		if ( empty( $service_id ) || get_post_type( $service_id ) !== 'gigneo_service' || 'publish' !== get_post_status( $service_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
		}

		$service_status = get_post_status( $service_id );

		if ( 'publish' !== $service_status ) {
			wp_send_json_error( array( 'message' => esc_html__( 'This service is currently not accepting the orders.', 'gigneo' ) ), 403 );
		}

		$service_author = get_post_field( 'post_author', $service_id );

		if ( absint( $service_author ) === $current_user->ID ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		if ( empty( $service_plan ) || ! in_array( $service_plan, array( 'one', 'two', 'three' ), true ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Invalid service price plan.', 'gigneo' ) ), 403 );
		}

		$service_plan_price = gigneo_post_meta( $service_id, "_gigneo_pm_price_plan_{$service_plan}_price" );

		if ( ! isset( $service_plan_price ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Invalid service price plan.', 'gigneo' ) ), 400 );
		}

		$service_author_profile_id = gigneo_profile_id( $service_author, 'freelancer' );

		do_action( 'gigneo_service_order_process_before_vacation_check', $service_author_profile_id );

		$service_plan_description        = gigneo_post_meta( $service_id, "_gigneo_pm_price_plan_{$service_plan}_description" );
		$service_plan_description        = ! empty( $service_plan_description ) ? $service_plan_description : 'unlimited';
		$service_plan_revisions          = gigneo_post_meta( $service_id, "_gigneo_pm_price_plan_{$service_plan}_revisions" );
		$service_plan_revisions          = ! empty( $service_plan_revisions ) ? $service_plan_revisions : 'unlimited';
		$service_plan_featured           = gigneo_post_meta( $service_id, "_gigneo_pm_price_plan_{$service_plan}_featured" );
		$service_plan_featured           = ! empty( $service_plan_featured ) ? $service_plan_featured : 'no';
		$service_plan_delivery_time      = gigneo_post_meta( $service_id, "_gigneo_pm_price_plan_{$service_plan}_delivery_time" );
		$service_plan_delivery_time      = ! empty( $service_plan_delivery_time ) ? absint( $service_plan_delivery_time ) : 0;
		$service_plan_delivery_time_type = gigneo_post_meta( $service_id, "_gigneo_pm_price_plan_{$service_plan}_delivery_time_type" );
		$service_plan_delivery_time_type = ! empty( $service_plan_delivery_time_type ) ? $service_plan_delivery_time_type : 'day';

		$service_plan_title = gigneo_service_price_plan( $service_plan );
		$service_plan_price = ! empty( $service_plan_price ) ? floatval( $service_plan_price ) : 0;

		$order_price_plan_meta = array(
			'id'                 => $service_plan,
			'title'              => $service_plan_title,
			'description'        => $service_plan_description,
			'price'              => $service_plan_price,
			'revisions'          => $service_plan_revisions,
			'featured'           => $service_plan_featured ?? 'no',
			'delivery_time'      => $service_plan_delivery_time,
			'delivery_time_type' => $service_plan_delivery_time_type,
		);

		$order_delivery_hours = gigneo_convert_to_hours( $service_plan_delivery_time, $service_plan_delivery_time_type );

		$order_subtotal_price = $service_plan_price;

		// Service features.
		$service_plan_features = array();

		$category_term_query = new WP_Term_Query(
			array(
				'taxonomy'   => 'gigneo_facet',
				'object_ids' => $service_id,
				'hide_empty' => false,
			)
		);

		if ( ! empty( $category_term_query->terms ) && ! is_wp_error( $category_term_query ) ) {
			foreach ( $category_term_query->terms as $term ) {
				$features = gigneo_term_meta( $term->term_id, '_gigneo_tm_service_features' );
				if ( ! empty( $features ) ) {
					foreach ( $features as $feature ) {
						if ( empty( $feature['slug'] ) || empty( $feature['title'] ) ) {
							continue;
						}
						$slug          = $feature['slug'];
						$title         = $feature['title'];
						$attribute_key = "_gigneo_pm_price_plan_{$service_plan}_feature_{$term->slug}_$slug";
						$feature_value = gigneo_post_meta( $service_id, $attribute_key );
						if ( ! empty( $feature_value ) ) {
							$service_plan_features[] = $title;
						}
					}
				}
			}
		}

		$order_price_plan_meta['features'] = $service_plan_features;

		// Custom Features.
		$service_plan_custom_features = array();
		$service_plan_custom_offers   = gigneo_post_meta( $service_id, '_gigneo_pm_custom_offers' );

		if ( ! empty( $service_plan_custom_offers ) && is_array( $service_plan_custom_offers ) ) {
			foreach ( $service_plan_custom_offers as $offer ) {
				$offer_title = ! empty( $offer['name'] ) ? $offer['name'] : '';
				$offer_value = ! empty( $offer[ 'plan_' . $service_plan ] ) ? $offer[ 'plan_' . $service_plan ] : '';
				if ( ! empty( $offer_value ) ) {
					$service_plan_custom_features[] = $offer_title;
				}
			}
		}

		$order_price_plan_meta['custom_offers'] = $service_plan_custom_features;

		// Addons.
		$service_addons_meta  = gigneo_post_meta( $service_id, '_gigneo_pm_addons' );
		$service_order_addons = ( isset( $_POST['addons'] ) && is_array( $_POST['addons'] ) ) ? map_deep( wp_unslash( $_POST['addons'] ), 'absint' ) : array();
		$order_addons_meta    = array();
		$service_addons_price = 0;
		$service_addons_hours = 0;

		if ( ! empty( $service_addons_meta ) && ! empty( $service_order_addons ) && is_array( $service_order_addons ) ) {
			foreach ( $service_order_addons as $service_addon ) {
				if ( ! isset( $service_addon['id'] ) || ! isset( $service_addon['quantity'] ) ) {
					continue;
				}
				$service_addon_id                 = absint( $service_addon['id'] );
				$service_addon_quantity           = absint( $service_addon['quantity'] );
				$service_addon_price              = intval( $service_addons_meta[ $service_addon_id ]['price'] ) ?? 0;
				$service_addon_price              = floatval( $service_addon_price ) ?? 0;
				$service_addons_price            += ( $service_addon_price * $service_addon_quantity );
				$service_addon_delivery_time      = intval( $service_addons_meta[ $service_addon_id ]['delivery_time'] ) ?? 1;
				$service_addon_delivery_time_type = $service_addons_meta[ $service_addon_id ]['time_type'] ?? 'day';

				$service_addons_hours += gigneo_convert_to_hours( ( $service_addon_delivery_time * $service_addon_quantity ), $service_addon_delivery_time_type );

				$order_addon = $service_addons_meta[ $service_addon_id ];

				unset( $order_addon['value'] );

				$order_addon['id']          = $service_addon_id;
				$order_addon['price']       = $service_addon_price;
				$order_addon['quantity']    = $service_addon_quantity;
				$order_addon['total_price'] = $service_addons_price;

				$order_addons_meta[] = $order_addon;
			}

			$order_delivery_hours += $service_addons_hours;
			$order_subtotal_price += $service_addons_price;
		}

		// Location.
		$service_type        = gigneo_post_meta( $service_id, '_gigneo_pm_type' );
		$order_location_meta = array();

		if ( 'onsite' === $service_type ) {

			$onsite_delivery_type = ! empty( $_POST['delivery_type'] ) ? sanitize_text_field( wp_unslash( $_POST['delivery_type'] ) ) : '';
			$location_id          = ! empty( $_POST['location_id'] ) ? sanitize_text_field( wp_unslash( $_POST['location_id'] ) ) : '';
			$location             = ! empty( $_POST['location'] ) ? sanitize_text_field( wp_unslash( $_POST['location'] ) ) : '';
			$location_address     = ! empty( $_POST['friendly_address'] ) ? sanitize_text_field( wp_unslash( $_POST['friendly_address'] ) ) : '';

			$map_distance_type      = gigneo_option( '_gigneo_ts_map_distance_type', 'kilometers' );
			$map_travel_charges     = gigneo_option( '_gigneo_ts_map_travel_charges', 2 );
			$service_locations_meta = gigneo_post_meta( $service_id, '_gigneo_pm_locations' );

			if ( isset( $location_id ) && isset( $service_locations_meta[ absint( $location_id ) ] ) ) {

				$order_location_meta = array(
					'id'            => absint( $location_id ),
					'type'          => $onsite_delivery_type,
					'country_state' => $service_locations_meta[ absint( $location_id ) ]['country_state'] ?? '',
					'city'          => $service_locations_meta[ absint( $location_id ) ]['city'] ?? '',
					'area'          => $service_locations_meta[ absint( $location_id ) ]['area'] ?? '',
					'zip_code'      => $service_locations_meta[ absint( $location_id ) ]['zip_code'] ?? '',
				);

				unset( $order_location_meta['location'] );

				$order_location_meta['location']['from']                     = $service_locations_meta[ absint( $location_id ) ]['location'][0] ?? array();
				$order_location_meta['location']['from']['friendly_address'] = $service_locations_meta[ absint( $location_id ) ]['address'] ?? '';

				if ( 'on_location' === $onsite_delivery_type ) {

					if ( empty( $location_address ) ) {
						wp_send_json_error( array( 'message' => esc_html__( 'Address is required.', 'gigneo' ) ), 400 );
					}

					$order_location_meta['location']['to']['friendly_address'] = $location_address;

					$charges_data = array(
						'order_subtotal_price' => $order_subtotal_price,
						'order_location_meta'  => $order_location_meta,
					);

					if ( is_string( $location ) ) {
						$location = json_decode( $location, true ) ?? array();
					} elseif ( ! is_array( $location ) ) {
						$location = array();
					}

					$charges_data = apply_filters(
						'gigneo_service_order_place_before',
						$charges_data,
						array(
							'location'           => $location,
							'location_address'   => $location_address,
							'map_distance_type'  => $map_distance_type,
							'map_travel_charges' => $map_travel_charges,
						)
					);

					$order_subtotal_price = $charges_data['order_subtotal_price'];
					$order_location_meta  = $charges_data['order_location_meta'];
					if ( isset( $charges_data['subtotal_without_travel_charges'] ) ) {
						$subtotal_without_travel_charges = $charges_data['subtotal_without_travel_charges'];
					}
				}
			}
		}

		// Validate and format intake form requirements if active.
		$service_intake_form_data = apply_filters( 'gigneo_service_order_place_validate_intake_form', array(), $service_id );

		if ( is_wp_error( $service_intake_form_data ) ) {
			wp_send_json_error( array( 'message' => $service_intake_form_data->get_error_message() ), 400 );
		}
		if ( isset( $subtotal_without_travel_charges ) ) {
			$order_platform_commission = gigneo_commission( $subtotal_without_travel_charges, 'service', $profile_id );
		} else {
			$order_platform_commission = gigneo_commission( $order_subtotal_price, 'service', $profile_id );
		}
		$order_platform_commission_total = isset( $order_platform_commission['total'] ) ? ( $order_platform_commission['total'] ) : 0;

		$order_total_price = $order_subtotal_price + $order_platform_commission_total;

		$wallet_amount = gigneo_wallet_balance( $profile_id );

		if ( $wallet_amount < $order_total_price ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Insufficient wallet balance.', 'gigneo' ) ), 400 );
		}

		$freelancer_profile_id = gigneo_profile_id( $service_author, 'freelancer' );

		if ( empty( $freelancer_profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
		}

		$order_args = array(
			'post_type'   => 'gigneo_order',
			'post_title'  => sprintf(
			/* translators: %s: Service order ID. */
				esc_html__( 'Service order - #%s', 'gigneo' ),
				gigneo_format_id( $service_id, 'service', false )
			),
			'post_status' => 'publish',
			'post_author' => $current_user->ID,
		);

		$order_id = wp_insert_post( $order_args );

		if ( is_wp_error( $order_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
		}

		update_post_meta( $order_id, '_gigneo_pm_order_type', 'service' );
		update_post_meta( $order_id, '_gigneo_pm_service_type', 'fixed' );
		update_post_meta( $order_id, '_gigneo_pm_service_id', $service_id );
		update_post_meta( $order_id, '_gigneo_pm_service_title', get_the_title( $service_id ) );
		update_post_meta( $order_id, '_gigneo_pm_profile_id', $profile_id );
		update_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id', $freelancer_profile_id );
		update_post_meta( $order_id, '_gigneo_pm_price_plan', $order_price_plan_meta );
		update_post_meta( $order_id, '_gigneo_pm_addons', $order_addons_meta );
		update_post_meta( $order_id, '_gigneo_pm_location', $order_location_meta );
		update_post_meta( $order_id, '_gigneo_pm_delivery_type', $service_type );
		update_post_meta( $order_id, '_gigneo_pm_delivery_hours', floatval( $order_delivery_hours ) );
		update_post_meta( $order_id, '_gigneo_pm_subtotal', floatval( $order_subtotal_price ) );
		update_post_meta( $order_id, '_gigneo_pm_commission', $order_platform_commission );
		update_post_meta( $order_id, '_gigneo_pm_total', floatval( $order_total_price ) );

		// Save intake form requirements.
		do_action( 'gigneo_service_order_placed_save_intake_form', $order_id, $service_id, $service_intake_form_data );

		// Create Invoice for Order.
		$order_invoice_args = array(
			'post_type'   => 'gigneo_invoice',
			'post_title'  => sprintf(
			// translators: %s is Order ID
				esc_html__( 'Service Order — %s', 'gigneo' ),
				esc_html( gigneo_format_id( $order_id, 'order', false ) )
			),
			'post_status' => 'paid',
			'post_author' => $current_user->ID,
		);

		$order_invoice_id = wp_insert_post( $order_invoice_args );

		if ( is_wp_error( $order_invoice_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
		}

		update_post_meta( $order_id, '_gigneo_pm_invoice_id', $order_invoice_id );

		update_post_meta( $order_invoice_id, '_gigneo_pm_type', 'service_order' );
		update_post_meta( $order_invoice_id, '_gigneo_pm_profile_id', $profile_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_order_id', $order_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_total', -floatval( $order_subtotal_price ) );

		// Create Invoice for Commission.
		if ( ! empty( $order_platform_commission_total ) ) {
			$commission_invoice_args = array(
				'post_type'   => 'gigneo_invoice',
				'post_title'  => sprintf(
				// translators: %s is Order ID
					esc_html__( 'Service Commission — %s', 'gigneo' ),
					esc_html( gigneo_format_id( $order_id, 'order', false ) )
				),
				'post_status' => 'paid',
				'post_author' => $current_user->ID,
			);

			$commission_invoice_id = wp_insert_post( $commission_invoice_args );

			if ( is_wp_error( $commission_invoice_id ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
			}

			update_post_meta( $order_id, '_gigneo_pm_commission_invoice_id', $commission_invoice_id );

			update_post_meta( $commission_invoice_id, '_gigneo_pm_type', 'service_commission' );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_profile_id', $profile_id );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_order_id', $order_id );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_total', -floatval( $order_platform_commission_total ) );
		}

		// Order Place Activity.
		$service_order_placed_activity = gigneo_trigger_order_activity( $order_id, 'order_placed' );

		if ( is_wp_error( $service_order_placed_activity ) ) {
			wp_send_json_error( array( 'message' => $service_order_placed_activity->get_error_message() ), 400 );
		}

		// Activity Stage - Order Placed.
		update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_placed' );

		// Order Placed Notification.
		$order_placed_nf_title = esc_html__( 'Service hired', 'gigneo' );

		$order_placed_nf_content = sprintf(
		// translators: 1: Service Title, 2: Profile Name
			esc_html__( 'Your service "%1$s" has been hired by %2$s.', 'gigneo' ),
			get_the_title( $service_id ),
			gigneo_profile_name( $profile_id )
		);

		Notifications::trigger_notification( $order_placed_nf_title, $order_placed_nf_content, 'order', 'order_placed', $freelancer_profile_id, $order_id );
		// Set initial activity stage and notifications.
		$stage_handled = apply_filters( 'gigneo_service_order_placed_stage_setup', false, $order_id, $service_id, $order_delivery_hours, $profile_id, $freelancer_profile_id );

		if ( ! $stage_handled ) {
			// Order Started Activity.
			$order_started_activity = gigneo_trigger_order_activity( $order_id, 'order_started' );
			if ( is_wp_error( $order_started_activity ) ) {
				wp_send_json_error( array( 'message' => $order_started_activity->get_error_message() ), 400 );
			}

			// Started At.
			update_post_meta( $order_id, '_gigneo_pm_started_at', current_time( 'mysql' ) );
			update_post_meta( $order_id, '_gigneo_pm_started_at_gmt', current_time( 'mysql', 1 ) );

			// Delivery Time.
			$delivery = new DateTime( 'now', wp_timezone() );
			$delivery->modify( "+$order_delivery_hours hours" );
			$delivery_local = $delivery->format( 'Y-m-d H:i:s' );

			$delivery_utc = clone $delivery;
			$delivery_utc->setTimezone( new DateTimeZone( 'UTC' ) );
			$delivery_gmt = $delivery_utc->format( 'Y-m-d H:i:s' );

			update_post_meta( $order_id, '_gigneo_pm_delivery_at', $delivery_local );
			update_post_meta( $order_id, '_gigneo_pm_delivery_at_gmt', $delivery_gmt );

			// Activity Stage - Order Started.
			update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_started' );
		}

		$response             = array();
		$response['redirect'] = gigneo_dashboard_url( 'order', 'service-detail', array( 'p_id' => $order_id ) );
		$response['message']  = esc_html__( 'Your order has been placed.', 'gigneo' );

		// Trigger Email.
		$wc_mailer = WC()->mailer();
		$wc_emails = $wc_mailer->get_emails();
		// Email for Freelancer.
		if ( isset( $wc_emails['Gigneo_Service_Hired'] ) ) {
			$wc_emails['Gigneo_Service_Hired']->trigger( $freelancer_profile_id, $order_id );
		}

		wp_send_json_success( $response );
	}

	/**
	 * Place Job order via AJAX.
	 *
	 * @throws DateMalformedStringException
	 * @since 0.0.1
	 */
	public function job_order_place() {

		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$profile_id   = gigneo_active_profile_id();
		$current_user = wp_get_current_user();

		if ( ! is_user_logged_in() ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Please log in to perform this action.', 'gigneo' ) ), 403 );
			exit;
		}

		if ( empty( $current_user->ID ) || empty( $profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$profile_type = gigneo_profile_type( $profile_id );

		if ( 'employer' !== $profile_type ) {
			wp_send_json_error(
				array(
					'message' => sprintf(
					// translators: %s is employer singular
						esc_html__( '%s can only hire job.', 'gigneo' ),
						gigneo_employer_singular()
					),
				),
				403
			);
		}

		$proposal_id = isset( $_POST['id'] ) ? absint( wp_unslash( $_POST['id'] ) ) : 0;

		$job_id     = gigneo_post_meta( $proposal_id, '_gigneo_pm_job_id' );
		$job_status = get_post_status( $job_id );
		$job_author = absint( get_post_field( 'post_author', $job_id ) );

		if ( empty( $proposal_id ) || empty( $job_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
		}

		if ( $current_user->ID !== $job_author ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		if ( 'publish' !== $job_status ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		if ( 'in-progress' === $job_status ) {
			wp_send_json_error( array( 'message' => esc_html__( 'This job is already hired.', 'gigneo' ) ), 403 );
		}
		$job_type                        = gigneo_post_meta( $job_id, '_gigneo_pm_type' );
		$freelancer_profile_id           = gigneo_post_meta( $proposal_id, '_gigneo_pm_profile_id' );
		$order_subtotal_price            = gigneo_post_meta( $proposal_id, '_gigneo_pm_budget' );
		$order_platform_commission       = gigneo_commission( $order_subtotal_price, 'job', $profile_id );
		$order_platform_commission_total = isset( $order_platform_commission['total'] ) ? ( $order_platform_commission['total'] ) : 0;

		$order_total_price = $order_subtotal_price + $order_platform_commission_total;

		$wallet_amount = gigneo_wallet_balance( $profile_id );

		if ( $wallet_amount < $order_total_price ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Insufficient wallet balance.', 'gigneo' ) ), 400 );
		}

		$order_args = array(
			'post_type'   => 'gigneo_order',
			'post_title'  => sprintf(
				// translators: %is proposal ID
				esc_html__( 'Job order — %s', 'gigneo' ),
				esc_html( gigneo_format_id( $proposal_id, 'proposal', false ) )
			),
			'post_status' => 'publish',
			'post_author' => $current_user->ID,
		);

		$order_id = wp_insert_post( $order_args );

		if ( is_wp_error( $order_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
		}
		$job_delivery_time      = gigneo_post_meta( $proposal_id, '_gigneo_pm_duration' );
		$job_delivery_time_type = gigneo_post_meta( $proposal_id, '_gigneo_pm_duration_type' );
		$order_delivery_hours   = gigneo_convert_to_hours( $job_delivery_time, $job_delivery_time_type );

		update_post_meta( $order_id, '_gigneo_pm_order_type', 'job' );
		update_post_meta( $order_id, '_gigneo_pm_job_type', 'fixed' );
		update_post_meta( $order_id, '_gigneo_pm_job_id', $job_id );
		update_post_meta( $order_id, '_gigneo_pm_job_title', get_the_title( $job_id ) );
		update_post_meta( $order_id, '_gigneo_pm_profile_id', $profile_id );
		update_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id', $freelancer_profile_id );

		update_post_meta( $order_id, '_gigneo_pm_proposal_id', $proposal_id );
		update_post_meta( $order_id, '_gigneo_pm_delivery_type', $job_type );
		update_post_meta( $order_id, '_gigneo_pm_delivery_hours', floatval( $order_delivery_hours ) );
		update_post_meta( $order_id, '_gigneo_pm_subtotal', floatval( $order_subtotal_price ) );
		update_post_meta( $order_id, '_gigneo_pm_commission', $order_platform_commission );
		update_post_meta( $order_id, '_gigneo_pm_total', floatval( $order_total_price ) );

		$job_info = array(
			'job_title'              => get_the_title( $job_id ),
			'job_id'                 => $job_id,
			'job_type'               => $job_type,
			'job_budget'             => $order_subtotal_price,
			'job_delivery_time'      => $job_delivery_time,
			'job_delivery_time_type' => $job_delivery_time_type,
		);

		update_post_meta( $proposal_id, '_gigneo_pm_order_id', $order_id );
		update_post_meta( $proposal_id, '_gigneo_pm_job_info', $job_info );

		wp_update_post(
			array(
				'ID'          => $proposal_id,
				'post_status' => 'hired',
			)
		);

		wp_update_post(
			array(
				'ID'          => $job_id,
				'post_status' => 'in-progress',
			)
		);

		delete_post_meta( $order_id, '_gigneo_pm_featured' );

		// Create Invoice for Order.
		$order_invoice_args = array(
			'post_type'   => 'gigneo_invoice',
			'post_title'  => sprintf(
			// translators: %s is Order ID
				esc_html__( 'Job Order — %s', 'gigneo' ),
				esc_html( gigneo_format_id( $order_id, 'order', false ) )
			),
			'post_status' => 'paid',
			'post_author' => $current_user->ID,
		);

		$order_invoice_id = wp_insert_post( $order_invoice_args );

		if ( is_wp_error( $order_invoice_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
		}

		update_post_meta( $order_id, '_gigneo_pm_invoice_id', $order_invoice_id );

		update_post_meta( $order_invoice_id, '_gigneo_pm_type', 'job_order' );
		update_post_meta( $order_invoice_id, '_gigneo_pm_profile_id', $profile_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_order_id', $order_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_total', -floatval( $order_subtotal_price ) );

		update_post_meta( $order_invoice_id, '_gigneo_pm_job_id', $job_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_proposal_id', $proposal_id );

		// Create Invoice for Commission.
		if ( ! empty( $order_platform_commission_total ) ) {
			$commission_invoice_args = array(
				'post_type'   => 'gigneo_invoice',
				'post_title'  => sprintf(
				// translators: %s is Order ID
					esc_html__( 'Job Commission — %s', 'gigneo' ),
					esc_html( gigneo_format_id( $order_id, 'order', false ) )
				),
				'post_status' => 'paid',
				'post_author' => $current_user->ID,
			);

			$commission_invoice_id = wp_insert_post( $commission_invoice_args );

			if ( is_wp_error( $commission_invoice_id ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
			}

			update_post_meta( $order_id, '_gigneo_pm_commission_invoice_id', $commission_invoice_id );

			update_post_meta( $commission_invoice_id, '_gigneo_pm_type', 'job_commission' );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_profile_id', $profile_id );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_order_id', $order_id );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_total', -floatval( $order_platform_commission_total ) );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_job_id', $job_id );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_proposal_id', $proposal_id );
		}

		// Order Place Activity.
		$job_order_placed_activity = gigneo_trigger_order_activity( $order_id, 'order_placed' );

		if ( is_wp_error( $job_order_placed_activity ) ) {
			wp_send_json_error( array( 'message' => $job_order_placed_activity->get_error_message() ), 400 );
		}

		// Activity Stage - Order Placed.
		update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_placed' );

		// Order Placed Notification.
		$order_placed_nf_title = esc_html__( 'Proposal hired', 'gigneo' );

		$order_placed_nf_content = sprintf(
		// translators: 1: Job Title, 2: Profile Name
			esc_html__( 'Your proposal on "%1$s" has been hired by %2$s.', 'gigneo' ),
			get_the_title( $job_id ),
			gigneo_profile_name( $profile_id )
		);

		Notifications::trigger_notification( $order_placed_nf_title, $order_placed_nf_content, 'order', 'order_placed', $freelancer_profile_id, $order_id );

		// Order Started Activity.
		$order_started_activity = gigneo_trigger_order_activity( $order_id, 'order_started' );
		if ( is_wp_error( $order_started_activity ) ) {
			wp_send_json_error( array( 'message' => $order_started_activity->get_error_message() ), 400 );
		}

		// Started At.
		update_post_meta( $order_id, '_gigneo_pm_started_at', current_time( 'mysql' ) );
		update_post_meta( $order_id, '_gigneo_pm_started_at_gmt', current_time( 'mysql', 1 ) );

		// Delivery Time.
		$delivery = new DateTime( 'now', wp_timezone() );
		$delivery->modify( "+$order_delivery_hours hours" );
		$delivery_local = $delivery->format( 'Y-m-d H:i:s' );

		$delivery_utc = clone $delivery;
		$delivery_utc->setTimezone( new DateTimeZone( 'UTC' ) );
		$delivery_gmt = $delivery_utc->format( 'Y-m-d H:i:s' );

		update_post_meta( $order_id, '_gigneo_pm_delivery_at', $delivery_local );
		update_post_meta( $order_id, '_gigneo_pm_delivery_at_gmt', $delivery_gmt );

		// Activity Stage - Order Started.
		update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_started' );

		$response             = array();
		$response['redirect'] = gigneo_dashboard_url( 'order', 'job-detail', array( 'p_id' => $order_id ) );
		$response['message']  = esc_html__( 'Your order has been placed.', 'gigneo' );

		// Trigger Email.
		$wc_mailer = WC()->mailer();
		$wc_emails = $wc_mailer->get_emails();
		// Email for Freelancer.
		if ( isset( $wc_emails['Gigneo_Job_Hired'] ) ) {
			$wc_emails['Gigneo_Job_Hired']->trigger( $freelancer_profile_id, $order_id );
		}

		wp_send_json_success( $response );
	}

	/**
	 * Handle service order delivery via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function service_order_deliver() {
		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$profile_id   = gigneo_active_profile_id();
		$profile_type = gigneo_profile_type( $profile_id );
		$current_user = wp_get_current_user();

		if ( empty( $current_user->ID ) || empty( $profile_id ) || 'freelancer' !== $profile_type ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$allowed_tags = gigneo_allowed_html_tags();

		$order_id    = isset( $_POST['order_id'] ) ? absint( wp_unslash( $_POST['order_id'] ) ) : 0;
		$type        = ! empty( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : '';
		$description = ! empty( $_POST['description'] ) ? wp_kses( wp_unslash( $_POST['description'] ), $allowed_tags ) : '';

		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- File uploads validated per-field below (name, tmp_name, size, extension).
		$files = isset( $_FILES['attachments'] ) ? $_FILES['attachments'] : array();

		if ( empty( $order_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
		}

		if ( empty( $type ) || empty( $description ) || empty( $files['name'] ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Required field could not be empty.', 'gigneo' ) ), 403 );
		}

		$service_title         = gigneo_post_meta( $order_id, '_gigneo_pm_service_title' );
		$freelancer_profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		$employer_profile_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );

		if ( empty( $freelancer_profile_id ) || absint( $profile_id ) !== absint( $freelancer_profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$final_delivery_activity = gigneo_post_meta( $order_id, '_gigneo_pm_delivery_activity' );

		if ( ! empty( $final_delivery_activity ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Cannot make another delivery until final delivery response.', 'gigneo' ) ), 403 );
		}

		$service_order_extensions = gigneo_option( '_gigneo_ts_service_order_extensions', array( 'jpg', 'jpeg', 'png', 'doc' ) );
		$service_order_max_files  = gigneo_option( '_gigneo_ts_service_order_max_files', '3' );
		$service_order_max_size   = gigneo_option( '_gigneo_ts_service_order_max_size', '1' );
		$max_file_size            = $service_order_max_size * 1024 * 1024;

		$files_collection = array();
		$files_uploaded   = array();

		if ( $files && is_array( $files['name'] ) ) {

			$total_files = count( $files['name'] );

			if ( $total_files > $service_order_max_files ) {
				wp_send_json_error(
					array(
						'message' => sprintf(
						// translators: %s: Maximum number of files allowed.
							esc_html__( 'You can only upload up to %s files.', 'gigneo' ),
							$service_order_max_files
						),
					),
					403
				);
			}

			for ( $i = 0; $i < $total_files; $i++ ) {

				$error = isset( $files['error'][ $i ] )
					? absint( $files['error'][ $i ] )
					: UPLOAD_ERR_NO_FILE;

				$size = isset( $files['size'][ $i ] )
					? absint( $files['size'][ $i ] )
					: 0;

				$name = isset( $files['name'][ $i ] )
					? sanitize_file_name( wp_unslash( $files['name'][ $i ] ) )
					: '';

				$tmp_name = isset( $files['tmp_name'][ $i ] )
					? sanitize_text_field( $files['tmp_name'][ $i ] )
					: '';

				$ext = strtolower( pathinfo( $name, PATHINFO_EXTENSION ) );

				// Check upload errors.
				if ( UPLOAD_ERR_OK !== $error ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: %s: File name. */
								esc_html__( 'Error uploading file: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				// Validate uploaded file.
				if ( empty( $tmp_name ) || ! is_uploaded_file( $tmp_name ) ) {
					wp_send_json_error(
						array(
							'message' => esc_html__( 'Invalid uploaded file.', 'gigneo' ),
						),
						403
					);
				}

				// Validate file extension.
				if ( ! in_array( $ext, $service_order_extensions, true ) ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: %s: File name. */
								esc_html__( 'File type not allowed: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				$filetype = wp_check_filetype_and_ext( $tmp_name, $name );

				if ( empty( $filetype['ext'] ) || empty( $filetype['type'] ) || $filetype['ext'] !== $ext ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: %s: File name. */
								esc_html__( 'File type not allowed: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				// Validate file size.
				if ( $size > $max_file_size ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: 1: File name, 2: Maximum file size in MB. */
								esc_html__( '%1$s exceeds the maximum file size of %2$s MB.', 'gigneo' ),
								esc_html( $name ),
								esc_html( $service_order_max_size )
							),
						),
						403
					);
				}

				$files_collection[] = array(
					'name'     => $name,
					'tmp_name' => $tmp_name,
					'size'     => $size,
					'ext'      => sanitize_key( $ext ),
				);
			}

			if ( empty( $files_collection ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
			}

			global $wpdb;
			$table_attachments = $wpdb->prefix . 'gigneo_attachments';
			$service_order_dir = gigneo_upload_dir_path( '/' . gmdate( 'Y' ) . '/' . gmdate( 'm' ) . '/order/' . $order_id );

			if ( ! file_exists( $service_order_dir ) ) {
				wp_mkdir_p( $service_order_dir );
			}

			foreach ( $files_collection as $file ) {
				$name     = $file['name'];
				$tmp_name = $file['tmp_name'];
				$size     = $file['size'];

				// Extract extension.
				$file_ext = pathinfo( $name, PATHINFO_EXTENSION );

				// Create custom filename with random suffix.
				$random_number = wp_rand( 1000, 9999 );
				$file_name     = pathinfo( sanitize_file_name( $name ), PATHINFO_FILENAME ) . '-' . $random_number . '.' . $file_ext;

				// Make sure it's unique in case same name already exists.
				$file_name = wp_unique_filename( $service_order_dir, $file_name );

				$file_path = $service_order_dir . '/' . $file_name;

				if ( is_uploaded_file( $tmp_name ) && gigneo_move_uploaded_file( $tmp_name, $file_path ) ) {

					$path = str_replace( gigneo_upload_dir_path(), '', $file_path );
					$url  = str_replace( gigneo_upload_dir_path(), gigneo_upload_dir_url(), $file_path );
					$meta = array( 'type' => 'deliverable' );

					// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
					$insert_file = $wpdb->insert(
						$table_attachments,
						array(
							'post_id'         => absint( $order_id ),
							'profile_id'      => absint( $profile_id ),
							'name'            => $name,
							'mime_type'       => $file_ext,
							'size'            => $size,
							'guid'            => $url,
							'path'            => $path,
							'post_type'       => 'order',
							'meta'            => maybe_serialize( $meta ),
							'uploaded_at'     => current_time( 'mysql' ),
							'uploaded_at_gmt' => current_time( 'mysql', 1 ),
						),
						array( '%d', '%d', '%s', '%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s' )
					);
					// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

					if ( false === $insert_file ) {
						wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
					}

					$files_uploaded[] = $wpdb->insert_id;
				}
			}
		}

		// Order Delivered.
		$order_deliver_activity_meta['_gigneo_cm_order_delivery_info'] = array(
			'status'      => 'pending',
			'description' => $description,
			'attachments' => $files_uploaded,
		);

		if ( 'final' === $type ) {
			$current_stage     = gigneo_post_meta( $order_id, '_gigneo_pm_activity_stage' );
			$revision_activity = gigneo_post_meta( $order_id, '_gigneo_pm_revision_activity' );

			$order_delivered_activity = gigneo_trigger_order_activity( $order_id, 'order_final_delivery', $profile_id, $order_deliver_activity_meta );
			if ( is_wp_error( $order_delivered_activity ) ) {
				wp_send_json_error( array( 'message' => $order_delivered_activity->get_error_message() ), 400 );
			}

			// Activity Stage - Order Delivered.
			if ( 'order_revision_requested' === $current_stage && ! empty( $revision_activity ) ) {
				$revision_delivery_info           = get_comment_meta( $revision_activity, '_gigneo_cm_revision_info', true );
				$revision_delivery_info['status'] = 'delivered';

				update_comment_meta( $revision_activity, '_gigneo_cm_revision_info', $revision_delivery_info );

				update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_revision_delivered' );
				delete_post_meta( $order_id, '_gigneo_pm_revision_activity' );
			} else {
				update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_delivered' );
			}

			update_post_meta( $order_id, '_gigneo_pm_delivery_activity', $order_delivered_activity );

			wp_update_post(
				array(
					'ID'          => $order_id,
					'post_status' => 'delivered',
				)
			);

			// Final Delivery Notification.
			$final_delivery_nf_title   = esc_html__( 'Final delivery', 'gigneo' );
			$final_delivery_nf_content = sprintf(
			// translators: 1: Service Title, 2: Profile Name
				esc_html__( 'The final delivery for the hired service "%1$s" has been submitted by %2$s.', 'gigneo' ),
				esc_html( $service_title ),
				esc_html( gigneo_profile_name( $profile_id ) )
			);

			// Trigger Email.
			$wc_mailer = WC()->mailer();
			$wc_emails = $wc_mailer->get_emails();
			// Email for Employer.
			if ( isset( $wc_emails['Gigneo_Service_Delivered'] ) ) {
				$wc_emails['Gigneo_Service_Delivered']->trigger( $employer_profile_id, $order_id );
			}

			Notifications::trigger_notification( $final_delivery_nf_title, $final_delivery_nf_content, 'order', 'final_delivery', $employer_profile_id, $order_id );

		} else {
			$order_delivered_activity = gigneo_trigger_order_activity( $order_id, 'order_draft_delivery', $profile_id, $order_deliver_activity_meta );
			if ( is_wp_error( $order_delivered_activity ) ) {
				wp_send_json_error( array( 'message' => $order_delivered_activity->get_error_message() ), 400 );
			}

			// Draft Delivery Notification.
			$draft_delivery_nf_title   = esc_html__( 'Draft delivery', 'gigneo' );
			$draft_delivery_nf_content = sprintf(
			// translators: 1: Service Title, 2: Profile Name
				esc_html__( 'The draft delivery for the hired service"%1$s" has been submitted by %2$s.', 'gigneo' ),
				esc_html( $service_title ),
				esc_html( gigneo_profile_name( $profile_id ) )
			);

			Notifications::trigger_notification( $draft_delivery_nf_title, $draft_delivery_nf_content, 'order', 'draft_delivery', $employer_profile_id, $order_id );

		}

		wp_send_json_success( array( 'message' => esc_html__( 'Order delivered successfully.', 'gigneo' ) ) );
	}

	/**
	 * Handle service order revision via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function service_order_revision() {
		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$profile_id   = gigneo_active_profile_id();
		$profile_type = gigneo_profile_type( $profile_id );
		$current_user = wp_get_current_user();

		if ( empty( $current_user->ID ) || empty( $profile_id ) || 'employer' !== $profile_type ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$allowed_tags = gigneo_allowed_html_tags();

		$order_id    = isset( $_POST['order_id'] ) ? absint( wp_unslash( $_POST['order_id'] ) ) : 0;
		$description = ! empty( $_POST['description'] ) ? wp_kses( wp_unslash( $_POST['description'] ), $allowed_tags ) : '';

		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- File uploads validated per-field below (name, tmp_name, size, extension).
		$files = isset( $_FILES['attachments'] ) ? $_FILES['attachments'] : array();

		if ( empty( $order_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
		}

		if ( empty( $description ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Required field could not be empty.', 'gigneo' ) ), 403 );
		}

		$employer_profile_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$freelancer_profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );

		if ( empty( $employer_profile_id ) || absint( $profile_id ) !== absint( $employer_profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$current_stage        = gigneo_post_meta( $order_id, '_gigneo_pm_activity_stage' );
		$delivery_activity_id = gigneo_post_meta( $order_id, '_gigneo_pm_delivery_activity' );

		if ( ( 'order_delivered' !== $current_stage && 'order_revision_delivered' !== $current_stage ) || empty( $delivery_activity_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
		}

		$revision_delivery_activity = gigneo_post_meta( $order_id, '_gigneo_pm_revision_activity' );

		if ( ! empty( $revision_delivery_activity ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Already revision requested.', 'gigneo' ) ), 403 );
		}

		$service_title  = gigneo_post_meta( $order_id, '_gigneo_pm_service_title' );
		$price_plan     = gigneo_post_meta( $order_id, '_gigneo_pm_price_plan' );
		$total_revision = ! empty( $price_plan['revisions'] ) ? $price_plan['revisions'] : 'unlimited';
		$revision_used  = gigneo_post_meta( $order_id, '_gigneo_pm_revision_used' );
		$revision_used  = ! empty( $revision_used ) ? intval( $revision_used ) : 0;
		$revision_left  = is_numeric( $total_revision ) ? intval( $total_revision ) - $revision_used : 0;

		if ( 'unlimited' !== $total_revision && $revision_left < 1 ) {
			wp_send_json_error( array( 'message' => esc_html__( 'No revisions left.', 'gigneo' ) ), 403 );
		}

		$service_order_extensions = gigneo_option( '_gigneo_ts_service_order_extensions', array( 'jpg', 'jpeg', 'png', 'doc' ) );
		$service_order_max_files  = gigneo_option( '_gigneo_ts_service_order_max_files', '3' );
		$service_order_max_size   = gigneo_option( '_gigneo_ts_service_order_max_size', '1' );
		$max_file_size            = $service_order_max_size * 1024 * 1024;

		$files_collection = array();
		$files_uploaded   = array();

		if ( $files && is_array( $files['name'] ) ) {

			$total_files = count( $files['name'] );

			if ( $total_files > $service_order_max_files ) {
				wp_send_json_error(
					array(
						'message' => sprintf(
						// translators: %s: Maximum number of files allowed.
							esc_html__( 'You can only upload up to %s files.', 'gigneo' ),
							$service_order_max_files
						),
					),
					403
				);
			}

			for ( $i = 0; $i < $total_files; $i++ ) {

				$error = isset( $files['error'][ $i ] )
					? absint( $files['error'][ $i ] )
					: UPLOAD_ERR_NO_FILE;

				$size = isset( $files['size'][ $i ] )
					? absint( $files['size'][ $i ] )
					: 0;

				$name = isset( $files['name'][ $i ] )
					? sanitize_file_name( wp_unslash( $files['name'][ $i ] ) )
					: '';

				$tmp_name = isset( $files['tmp_name'][ $i ] )
					? sanitize_text_field( $files['tmp_name'][ $i ] )
					: '';

				$ext = strtolower( pathinfo( $name, PATHINFO_EXTENSION ) );

				// Check upload errors.
				if ( UPLOAD_ERR_OK !== $error ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: %s: File name. */
								esc_html__( 'Error uploading file: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				// Validate uploaded file.
				if ( empty( $tmp_name ) || ! is_uploaded_file( $tmp_name ) ) {
					wp_send_json_error(
						array(
							'message' => esc_html__( 'Invalid uploaded file.', 'gigneo' ),
						),
						403
					);
				}

				// Validate file extension.
				if ( ! in_array( $ext, $service_order_extensions, true ) ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: %s: File name. */
								esc_html__( 'File type not allowed: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				$filetype = wp_check_filetype_and_ext( $tmp_name, $name );

				if ( empty( $filetype['ext'] ) || empty( $filetype['type'] ) || $filetype['ext'] !== $ext ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: %s: File name. */
								esc_html__( 'File type not allowed: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				// Validate file size.
				if ( $size > $max_file_size ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: 1: File name, 2: Maximum file size in MB. */
								esc_html__( '%1$s exceeds the maximum file size of %2$s MB.', 'gigneo' ),
								esc_html( $name ),
								esc_html( $service_order_max_size )
							),
						),
						403
					);
				}

				$files_collection[] = array(
					'name'     => $name,
					'tmp_name' => $tmp_name,
					'size'     => $size,
					'ext'      => sanitize_key( $ext ),
				);
			}

			if ( empty( $files_collection ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
			}

			global $wpdb;
			$table_attachments = $wpdb->prefix . 'gigneo_attachments';
			$service_order_dir = gigneo_upload_dir_path( '/' . gmdate( 'Y' ) . '/' . gmdate( 'm' ) . '/order/' . $order_id );

			if ( ! file_exists( $service_order_dir ) ) {
				wp_mkdir_p( $service_order_dir );
			}

			foreach ( $files_collection as $file ) {
				$name     = $file['name'];
				$tmp_name = $file['tmp_name'];
				$size     = $file['size'];

				// Extract extension.
				$file_ext = pathinfo( $name, PATHINFO_EXTENSION );

				// Create custom filename with random suffix.
				$random_number = wp_rand( 1000, 9999 );
				$file_name     = pathinfo( sanitize_file_name( $name ), PATHINFO_FILENAME ) . '-' . $random_number . '.' . $file_ext;

				// Make sure it's unique in case same name already exists.
				$file_name = wp_unique_filename( $service_order_dir, $file_name );

				$file_path = $service_order_dir . '/' . $file_name;

				if ( is_uploaded_file( $tmp_name ) && gigneo_move_uploaded_file( $tmp_name, $file_path ) ) {

					$path = str_replace( gigneo_upload_dir_path(), '', $file_path );
					$url  = str_replace( gigneo_upload_dir_path(), gigneo_upload_dir_url(), $file_path );
					$meta = array( 'type' => 'revision' );

					// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
					$insert_file = $wpdb->insert(
						$table_attachments,
						array(
							'post_id'         => absint( $order_id ),
							'profile_id'      => absint( $profile_id ),
							'name'            => $name,
							'mime_type'       => $file_ext,
							'size'            => $size,
							'guid'            => $url,
							'path'            => $path,
							'post_type'       => 'order',
							'meta'            => maybe_serialize( $meta ),
							'uploaded_at'     => current_time( 'mysql' ),
							'uploaded_at_gmt' => current_time( 'mysql', 1 ),
						),
						array( '%d', '%d', '%s', '%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s' )
					);
					// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

					if ( false === $insert_file ) {
						wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
					}

					$files_uploaded[] = $wpdb->insert_id;
				}
			}
		}

		// Order Revision.
		$order_revision_activity_meta                             = array();
		$order_revision_activity_meta['_gigneo_cm_revision_info'] = array(
			'status'      => 'requested',
			'description' => $description,
			'attachments' => $files_uploaded,
		);

		$order_revision_activity_meta['_gigneo_cm_order_revision_status'] = 'pending';

		$order_revision_activity = gigneo_trigger_order_activity( $order_id, 'order_revision_requested', $profile_id, $order_revision_activity_meta );
		if ( is_wp_error( $order_revision_activity ) ) {
			wp_send_json_error( array( 'message' => $order_revision_activity->get_error_message() ), 400 );
		}

		$order_deliver_activity_meta           = get_comment_meta( $delivery_activity_id, '_gigneo_cm_order_delivery_info', true );
		$order_deliver_activity_meta['status'] = 'revision';

		update_comment_meta( $delivery_activity_id, '_gigneo_cm_order_delivery_info', $order_deliver_activity_meta );

		// Activity Stage - Order Revision.
		update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_revision_requested' );
		update_post_meta( $order_id, '_gigneo_pm_revision_activity', $order_revision_activity );
		update_post_meta( $order_id, '_gigneo_pm_revision_used', $revision_used + 1 );

		delete_post_meta( $order_id, '_gigneo_pm_delivery_activity' );

		wp_update_post(
			array(
				'ID'          => $order_id,
				'post_status' => 'publish',
			)
		);

		// Revision Notification.
		$revision_nf_title   = esc_html__( 'Revision requested', 'gigneo' );
		$revision_nf_content = sprintf(
		// translators: 1: Service Title, 2: Profile Name
			esc_html__( 'Revision requested for the hired service "%1$s" by %2$s.', 'gigneo' ),
			esc_html( $service_title ),
			esc_html( gigneo_profile_name( $profile_id ) )
		);

		Notifications::trigger_notification( $revision_nf_title, $revision_nf_content, 'order', 'revision_requested', $freelancer_profile_id, $order_id );

		// Trigger Email.
		$wc_mailer = WC()->mailer();
		$wc_emails = $wc_mailer->get_emails();
		// Email for Freelancer.
		if ( isset( $wc_emails['Gigneo_Service_Revision'] ) ) {
			$wc_emails['Gigneo_Service_Revision']->trigger( $freelancer_profile_id, $order_id );
		}

		wp_send_json_success( array( 'message' => esc_html__( 'Revision requested successfully.', 'gigneo' ) ) );
	}

	/**
	 * Approve service order delivery via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function service_order_delivery_approve() {
		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$profile_id   = gigneo_active_profile_id();
		$profile_type = gigneo_profile_type( $profile_id );
		$current_user = wp_get_current_user();

		if ( empty( $current_user->ID ) || empty( $profile_id ) || 'employer' !== $profile_type ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$order_id     = isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0;
		$order_status = get_post_status( $order_id );

		if ( empty( $order_id ) || 'delivered' !== $order_status ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$employer_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$freelancer_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );

		if ( absint( $employer_id ) !== absint( $profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$current_stage        = gigneo_post_meta( $order_id, '_gigneo_pm_activity_stage' );
		$delivery_activity_id = gigneo_post_meta( $order_id, '_gigneo_pm_delivery_activity' );
		$service_id           = gigneo_post_meta( $order_id, '_gigneo_pm_service_id' );

		if ( ( 'order_delivered' !== $current_stage && 'order_revision_delivered' !== $current_stage ) || empty( $delivery_activity_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
		}

		$order_delivery_approved_activity = gigneo_trigger_order_activity( $order_id, 'order_delivery_approved', $profile_id );
		if ( is_wp_error( $order_delivery_approved_activity ) ) {
			wp_send_json_error( array( 'message' => $order_delivery_approved_activity->get_error_message() ), 400 );
		}

		$order_complete_activity = gigneo_trigger_order_activity( $order_id, 'order_completed', $profile_id );
		if ( is_wp_error( $order_complete_activity ) ) {
			wp_send_json_error( array( 'message' => $order_complete_activity->get_error_message() ), 400 );
		}

		$order_deliver_activity_meta           = get_comment_meta( $delivery_activity_id, '_gigneo_cm_order_delivery_info', true );
		$order_deliver_activity_meta['status'] = 'approved';

		update_comment_meta( $delivery_activity_id, '_gigneo_cm_order_delivery_info', $order_deliver_activity_meta );

		// Activity Stage - Order Completed.
		update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_completed' );

		$total_order_completed = gigneo_post_meta( $service_id, '_gigneo_pm_total_completed_order' );
		update_post_meta( $service_id, '_gigneo_pm_total_completed_order', intval( $total_order_completed ) + 1 );

		$freelancer_user_id              = get_post_field( 'post_author', $freelancer_id );
		$order_total                     = gigneo_post_meta( $order_id, '_gigneo_pm_subtotal' );
		$order_location                  = gigneo_post_meta( $order_id, '_gigneo_pm_location' );
		$travel_charges                  = isset( $order_location['total_travel_charges'] ) ? floatval( $order_location['total_travel_charges'] ) : 0;
		$commission_total                = $order_total - $travel_charges;
		$order_platform_commission       = gigneo_commission( $commission_total, 'service', $freelancer_id );
		$order_platform_commission_total = $order_platform_commission['total'] ?? 0;

		// Create Invoice for Order.
		$order_invoice_args = array(
			'post_type'   => 'gigneo_invoice',
			'post_title'  => sprintf(
			// translators: %s is Order ID
				esc_html__( 'Service order completed — %s', 'gigneo' ),
				esc_html( gigneo_format_id( $order_id, 'order', false ) )
			),
			'post_status' => 'completed',
			'post_author' => $freelancer_user_id,
		);

		$order_invoice_id = wp_insert_post( $order_invoice_args );

		if ( is_wp_error( $order_invoice_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
		}

		update_post_meta( $order_id, '_gigneo_pm_freelancer_invoice_id', $order_invoice_id );

		update_post_meta( $order_invoice_id, '_gigneo_pm_type', 'service_order' );
		update_post_meta( $order_invoice_id, '_gigneo_pm_profile_id', $freelancer_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_order_id', $order_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_total', floatval( $order_total ) );

		// Create Invoice for Commission.
		if ( ! empty( $order_platform_commission_total ) ) {
			$commission_invoice_args = array(
				'post_type'   => 'gigneo_invoice',
				'post_title'  => sprintf(
				// translators: %s is Order ID
					esc_html__( 'Service commission — %s', 'gigneo' ),
					esc_html( gigneo_format_id( $order_id, 'order', false ) )
				),
				'post_status' => 'completed',
				'post_author' => $freelancer_user_id,
			);

			$commission_invoice_id = wp_insert_post( $commission_invoice_args );

			if ( is_wp_error( $commission_invoice_id ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
			}

			update_post_meta( $order_id, '_gigneo_pm_commission_invoice_id', $commission_invoice_id );

			update_post_meta( $commission_invoice_id, '_gigneo_pm_type', 'service_commission' );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_profile_id', $freelancer_id );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_order_id', $order_id );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_commission', $order_platform_commission );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_total', -floatval( $order_platform_commission_total ) );
		}

		$order_payment_activity = gigneo_trigger_order_activity( $order_id, 'order_payment_released', $profile_id );
		if ( is_wp_error( $order_payment_activity ) ) {
			wp_send_json_error( array( 'message' => $order_payment_activity->get_error_message() ), 400 );
		}

		wp_update_post(
			array(
				'ID'          => $order_id,
				'post_status' => 'completed',
			)
		);

		update_post_meta( $order_id, '_gigneo_pm_completed_at', current_time( 'mysql' ) );
		update_post_meta( $order_id, '_gigneo_pm_completed_at_gmt', current_time( 'mysql', 1 ) );

		$service_id            = gigneo_post_meta( $order_id, '_gigneo_pm_service_id' );
		$freelancer_profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );

		$completed_orders_freelancer = gigneo_profile_stat( 'completed_orders_count', $freelancer_profile_id );
		$completed_orders_employer   = gigneo_profile_stat( 'completed_orders_count', $profile_id );

		update_post_meta( $freelancer_profile_id, '_gigneo_pm_orders_completed', intval( $completed_orders_freelancer['lifetime'] ) );
		update_post_meta( $profile_id, '_gigneo_pm_orders_completed', intval( $completed_orders_employer['lifetime'] ) );

		// Order Approve Notification.
		$order_approve_nf_title   = esc_html__( 'Delivery approved', 'gigneo' );
		$order_approve_nf_content = sprintf(
		// translators: 1: Service Title, 2: Profile Name
			esc_html__( 'Your service delivery "%1$s" has been approved by %2$s.', 'gigneo' ),
			get_the_title( $service_id ),
			gigneo_profile_name( $profile_id )
		);

		Notifications::trigger_notification( $order_approve_nf_title, $order_approve_nf_content, 'order', 'order_delivery_approve', $freelancer_profile_id, $order_id );

		// Complete Notification.
		$order_complete_nf_title   = esc_html__( 'Order completed', 'gigneo' );
		$order_complete_nf_content = sprintf(
		// translators: 1: Service Title, 2: Profile Name
			esc_html__( 'The order for the hired service "%1$s" has been completed by %2$s.', 'gigneo' ),
			get_the_title( $service_id ),
			esc_html( gigneo_profile_name( $profile_id ) )
		);

		Notifications::trigger_notification( $order_complete_nf_title, $order_complete_nf_content, 'order', 'order_completed', $freelancer_id, $order_id );

		// Trigger Email.
		$wc_mailer  = WC()->mailer();
		$wc_emails  = $wc_mailer->get_emails();
		$profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		// Email for Freelancer.
		if ( isset( $wc_emails['Gigneo_Service_Completed'] ) ) {
			$wc_emails['Gigneo_Service_Completed']->trigger( $profile_id, $order_id );
		}

		wp_send_json_success( array( 'message' => esc_html__( 'Order delivery approved successfully.', 'gigneo' ) ), 200 );
	}

	/**
	 * Approve service order complete via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function service_order_complete() {
		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$profile_id   = gigneo_active_profile_id();
		$profile_type = gigneo_profile_type( $profile_id );
		$current_user = wp_get_current_user();

		if ( empty( $current_user->ID ) || empty( $profile_id ) || 'employer' !== $profile_type ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$order_id     = isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0;
		$order_status = get_post_status( $order_id );

		if ( empty( $order_id ) || ! in_array( $order_status, array( 'publish', 'delivered' ), true ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$service_title = gigneo_post_meta( $order_id, '_gigneo_pm_service_title' );
		$employer_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$freelancer_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		$service_id    = gigneo_post_meta( $order_id, '_gigneo_pm_service_id' );

		if ( absint( $employer_id ) !== absint( $profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$order_complete_activity = gigneo_trigger_order_activity( $order_id, 'order_completed', $profile_id );
		if ( is_wp_error( $order_complete_activity ) ) {
			wp_send_json_error( array( 'message' => $order_complete_activity->get_error_message() ), 400 );
		}

		// Activity Stage - Order Completed.
		update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_completed' );

		$total_order_completed = gigneo_post_meta( $service_id, '_gigneo_pm_total_completed_order' );
		update_post_meta( $service_id, '_gigneo_pm_total_completed_order', intval( $total_order_completed ) + 1 );

		$freelancer_user_id              = get_post_field( 'post_author', $freelancer_id );
		$order_total                     = gigneo_post_meta( $order_id, '_gigneo_pm_subtotal' );
		$order_location                  = gigneo_post_meta( $order_id, '_gigneo_pm_location' );
		$travel_charges                  = isset( $order_location['total_travel_charges'] ) ? floatval( $order_location['total_travel_charges'] ) : 0;
		$commission_total                = $order_total - $travel_charges;
		$order_platform_commission       = gigneo_commission( $commission_total, 'service', $freelancer_id );
		$order_platform_commission_total = $order_platform_commission['total'] ?? 0;

		// Create Invoice for Order.
		$order_invoice_args = array(
			'post_type'   => 'gigneo_invoice',
			'post_title'  => sprintf(
			// translators: %s is Order ID
				esc_html__( 'Service order completed — %s', 'gigneo' ),
				esc_html( gigneo_format_id( $order_id, 'order', false ) )
			),
			'post_status' => 'completed',
			'post_author' => $freelancer_user_id,
		);

		$order_invoice_id = wp_insert_post( $order_invoice_args );

		if ( is_wp_error( $order_invoice_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
		}

		update_post_meta( $order_id, '_gigneo_pm_freelancer_invoice_id', $order_invoice_id );

		update_post_meta( $order_invoice_id, '_gigneo_pm_type', 'service_order' );
		update_post_meta( $order_invoice_id, '_gigneo_pm_profile_id', $freelancer_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_order_id', $order_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_total', floatval( $order_total ) );

		// Create Invoice for Commission.
		if ( ! empty( $order_platform_commission_total ) ) {
			$commission_invoice_args = array(
				'post_type'   => 'gigneo_invoice',
				'post_title'  => sprintf(
				// translators: %s is Order ID
					esc_html__( 'Service Commission — #%s', 'gigneo' ),
					esc_html( gigneo_format_id( $order_id, 'order', false ) )
				),
				'post_status' => 'paid',
				'post_author' => $freelancer_user_id,
			);

			$commission_invoice_id = wp_insert_post( $commission_invoice_args );

			if ( is_wp_error( $commission_invoice_id ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
			}

			update_post_meta( $order_id, '_gigneo_pm_commission_invoice_id', $commission_invoice_id );

			update_post_meta( $commission_invoice_id, '_gigneo_pm_type', 'service_commission' );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_profile_id', $freelancer_id );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_order_id', $order_id );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_total', -floatval( $order_platform_commission_total ) );
		}

		$order_payment_activity = gigneo_trigger_order_activity( $order_id, 'order_payment_released', $profile_id );
		if ( is_wp_error( $order_payment_activity ) ) {
			wp_send_json_error( array( 'message' => $order_payment_activity->get_error_message() ), 400 );
		}

		wp_update_post(
			array(
				'ID'          => $order_id,
				'post_status' => 'completed',
			)
		);

		update_post_meta( $order_id, '_gigneo_pm_completed_at', current_time( 'mysql' ) );
		update_post_meta( $order_id, '_gigneo_pm_completed_at_gmt', current_time( 'mysql', 1 ) );

		$freelancer_profile_id       = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		$completed_orders_freelancer = gigneo_profile_stat( 'completed_orders_count', $freelancer_profile_id );
		$completed_orders_employer   = gigneo_profile_stat( 'completed_orders_count', $profile_id );

		update_post_meta( $freelancer_profile_id, '_gigneo_pm_orders_completed', intval( $completed_orders_freelancer['lifetime'] ) );
		update_post_meta( $profile_id, '_gigneo_pm_orders_completed', intval( $completed_orders_employer['lifetime'] ) );

		// Complete Notification.
		$order_complete_nf_title   = esc_html__( 'Order completed', 'gigneo' );
		$order_complete_nf_content = sprintf(
		// translators: 1: Service Title, 2: Profile Name
			esc_html__( 'The order for the hired service "%1$s" has been completed by %2$s.', 'gigneo' ),
			esc_html( $service_title ),
			esc_html( gigneo_profile_name( $profile_id ) )
		);

		Notifications::trigger_notification( $order_complete_nf_title, $order_complete_nf_content, 'order', 'order_completed', $freelancer_id, $order_id );

		// Trigger Email.
		$wc_mailer = WC()->mailer();
		$wc_emails = $wc_mailer->get_emails();
		// Email for Freelancer.
		if ( isset( $wc_emails['Gigneo_Service_Completed'] ) ) {
			$wc_emails['Gigneo_Service_Completed']->trigger( $freelancer_profile_id, $order_id );
		}

		wp_send_json_success( array( 'message' => esc_html__( 'Order complete successfully.', 'gigneo' ) ), 200 );
	}

	/**
	 * Service order cancel via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function service_order_cancel() {
		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$profile_id   = gigneo_active_profile_id();
		$profile_type = gigneo_profile_type( $profile_id );
		$current_user = wp_get_current_user();

		if ( empty( $current_user->ID ) || empty( $profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$order_id     = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;
		$order_status = get_post_status( $order_id );

		if ( empty( $order_id ) || ! in_array( $order_status, array( 'publish', 'delivered' ), true ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$employer_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$freelancer_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );

		if ( absint( $freelancer_id ) !== absint( $profile_id ) && absint( $employer_id ) !== absint( $profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$response = array();

		if ( 'freelancer' === $profile_type ) {

			$order_cancel_activity = gigneo_trigger_order_activity( $order_id, 'order_cancelled', $profile_id );
			if ( is_wp_error( $order_cancel_activity ) ) {
				wp_send_json_error( array( 'message' => $order_cancel_activity->get_error_message() ), 400 );
			}

			$employer_user_id                = get_post_field( 'post_author', $employer_id );
			$order_total                     = gigneo_post_meta( $order_id, '_gigneo_pm_subtotal' );
			$order_platform_commission       = gigneo_post_meta( $order_id, '_gigneo_pm_commission' );
			$order_platform_commission_total = $order_platform_commission['total'] ?? 0;

			// Create Invoice for Order.
			$order_invoice_args = array(
				'post_type'   => 'gigneo_invoice',
				'post_title'  => sprintf(
				// translators: %s is Order ID
					esc_html__( 'Service order refunded — %s', 'gigneo' ),
					esc_html( gigneo_format_id( $order_id, 'order', false ) )
				),
				'post_status' => 'refunded',
				'post_author' => $employer_user_id,
			);

			$order_invoice_id = wp_insert_post( $order_invoice_args );

			if ( is_wp_error( $order_invoice_id ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
			}

			update_post_meta( $order_id, '_gigneo_pm_refund_invoice_id', $order_invoice_id );

			update_post_meta( $order_invoice_id, '_gigneo_pm_type', 'service_order' );
			update_post_meta( $order_invoice_id, '_gigneo_pm_profile_id', $employer_id );
			update_post_meta( $order_invoice_id, '_gigneo_pm_order_id', $order_id );
			update_post_meta( $order_invoice_id, '_gigneo_pm_total', floatval( $order_total ) );

			$employer_user_id = get_post_field( 'post_author', $employer_id );

			// Create Invoice for Commission.
			if ( ! empty( $order_platform_commission_total ) ) {
				$commission_invoice_args = array(
					'post_type'   => 'gigneo_invoice',
					'post_title'  => sprintf(
					// translators: %s is Order ID
						esc_html__( 'Service commission refunded — %s', 'gigneo' ),
						esc_html( gigneo_format_id( $order_id, 'order', false ) )
					),
					'post_status' => 'refunded',
					'post_author' => $employer_user_id,
				);

				$commission_invoice_id = wp_insert_post( $commission_invoice_args );

				if ( is_wp_error( $commission_invoice_id ) ) {
					wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
				}

				update_post_meta( $order_id, '_gigneo_pm_commission_refund_invoice_id', $order_invoice_id );

				update_post_meta( $commission_invoice_id, '_gigneo_pm_type', 'service_commission' );
				update_post_meta( $commission_invoice_id, '_gigneo_pm_profile_id', $employer_id );
				update_post_meta( $commission_invoice_id, '_gigneo_pm_order_id', $order_id );
				update_post_meta( $commission_invoice_id, '_gigneo_pm_total', floatval( $order_platform_commission_total ) );
			}

			// Activity Stage - Order Cancelled.
			update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_cancelled' );

			// Payment Refunded Activity.
			$order_payment_activity = gigneo_trigger_order_activity( $order_id, 'order_payment_refunded', $profile_id );
			if ( is_wp_error( $order_payment_activity ) ) {
				wp_send_json_error( array( 'message' => $order_payment_activity->get_error_message() ), 400 );
			}

			wp_update_post(
				array(
					'ID'          => $order_id,
					'post_status' => 'cancelled',
				)
			);

			update_post_meta( $order_id, '_gigneo_pm_cancelled_at', current_time( 'mysql' ) );
			update_post_meta( $order_id, '_gigneo_pm_cancelled_at_gmt', current_time( 'mysql', 1 ) );

			$response['message'] = esc_html__( 'Order cancelled successfully.', 'gigneo' );

			$employer_profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
			$service_id          = gigneo_post_meta( $order_id, '_gigneo_pm_service_id' );
			// Trigger Email.
			$wc_mailer = WC()->mailer();
			$wc_emails = $wc_mailer->get_emails();
			// Email for Employer.
			if ( isset( $wc_emails['Gigneo_Service_Cancelled'] ) ) {
				$wc_emails['Gigneo_Service_Cancelled']->trigger( $employer_profile_id, $order_id );
			}

			// Order Notification.
			$order_cancel_nf_title   = esc_html__( 'Order cancelled', 'gigneo' );
			$order_cancel_nf_content = sprintf(
			// translators: 1: Service Title, 2: Profile Name
				esc_html__( 'Your service order "%1$s" has been cancelled by %2$s.', 'gigneo' ),
				get_the_title( $service_id ),
				gigneo_profile_name( $profile_id )
			);

			Notifications::trigger_notification( $order_cancel_nf_title, $order_cancel_nf_content, 'order', 'order_cancelled', $employer_profile_id, $order_id );

		} else {

			$allowed_tags = gigneo_allowed_html_tags();

			$reason      = isset( $_POST['order_cancel_reason'] ) ? sanitize_text_field( wp_unslash( $_POST['order_cancel_reason'] ) ) : '';
			$description = isset( $_POST['order_cancel_description'] ) ? wp_kses( wp_unslash( $_POST['order_cancel_description'] ), $allowed_tags ) : '';

			if ( empty( $reason ) || empty( $description ) ) {
				wp_send_json_error( array( esc_html__( 'Required field could not be empty.', 'gigneo' ) ), 403 );
			}

			$cancel_request_activity = gigneo_post_meta( $order_id, '_gigneo_pm_cancel_request_activity' );

			if ( ! empty( $cancel_request_activity ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Cancel request already submitted.', 'gigneo' ) ), 400 );
			}

			$cancel_activity_meta['_gigneo_cm_cancel_request_info'] = array(
				'status'      => 'requested',
				'reason'      => $reason,
				'description' => $description,
			);

			$order_cancel_activity = gigneo_trigger_order_activity( $order_id, 'order_cancel_requested', $profile_id, $cancel_activity_meta );
			if ( is_wp_error( $order_cancel_activity ) ) {
				wp_send_json_error( array( 'message' => $order_cancel_activity->get_error_message() ), 400 );
			}

			update_post_meta( $order_id, '_gigneo_pm_cancel_request_activity', $order_cancel_activity );

			$freelancer_profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
			$service_id            = gigneo_post_meta( $order_id, '_gigneo_pm_service_id' );

			// Order Notification.
			$order_cancel_req_nf_title   = esc_html__( 'Order cancel request', 'gigneo' );
			$order_cancel_req_nf_content = sprintf(
			// translators: 1: Service Title, 2: Profile Name
				esc_html__( 'Your service order "%1$s" has been requested to cancelled by %2$s.', 'gigneo' ),
				get_the_title( $service_id ),
				gigneo_profile_name( $profile_id )
			);

			Notifications::trigger_notification( $order_cancel_req_nf_title, $order_cancel_req_nf_content, 'order', 'order_cancel_request', $freelancer_profile_id, $order_id );

			// Trigger Email.
			$wc_mailer = WC()->mailer();
			$wc_emails = $wc_mailer->get_emails();
			// Email for Freelancer.
			if ( isset( $wc_emails['Gigneo_Service_Cancel_Request'] ) ) {
				$wc_emails['Gigneo_Service_Cancel_Request']->trigger( $freelancer_profile_id, $order_id );
			}

			$response['message'] = esc_html__( 'Order cancel request submitted successfully.', 'gigneo' );
		}

		wp_send_json_success( $response, 200 );
	}

	/**
	 * Respond to a service order cancel request via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function service_order_cancel_respond() {

		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$profile_id   = gigneo_active_profile_id();
		$profile_type = gigneo_profile_type( $profile_id );
		$current_user = wp_get_current_user();

		if ( empty( $current_user->ID ) || empty( $profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$order_id = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;
		$type     = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : '';

		if ( empty( $order_id ) || 'freelancer' !== $profile_type ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$freelancer_id           = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		$employer_id             = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$cancel_request_activity = gigneo_post_meta( $order_id, '_gigneo_pm_cancel_request_activity' );
		$service_id              = gigneo_post_meta( $order_id, '_gigneo_pm_service_id' );

		if ( absint( $freelancer_id ) !== absint( $profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		if ( empty( $type ) && empty( $cancel_request_activity ) && 'approve' !== $type && 'decline' !== $type ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
		}

		$response = array();

		if ( 'approve' === $type ) {

			$order_cancel_approve_activity = gigneo_trigger_order_activity( $order_id, 'order_cancel_request_approved', $profile_id );
			if ( is_wp_error( $order_cancel_approve_activity ) ) {
				wp_send_json_error( array( 'message' => $order_cancel_approve_activity->get_error_message() ), 400 );
			}

			$cancel_activity_meta           = get_comment_meta( $cancel_request_activity, '_gigneo_cm_cancel_request_info', true );
			$cancel_activity_meta['status'] = 'approved';

			update_comment_meta( $cancel_request_activity, '_gigneo_cm_cancel_request_info', $cancel_activity_meta );

			delete_post_meta( $order_id, '_gigneo_pm_cancel_request_activity' );

			$order_cancel_activity = gigneo_trigger_order_activity( $order_id, 'order_cancelled', $profile_id );
			if ( is_wp_error( $order_cancel_activity ) ) {
				wp_send_json_error( array( 'message' => $order_cancel_activity->get_error_message() ), 400 );
			}

			$employer_user_id                = get_post_field( 'post_author', $employer_id );
			$order_total                     = gigneo_post_meta( $order_id, '_gigneo_pm_subtotal' );
			$order_platform_commission       = gigneo_post_meta( $order_id, '_gigneo_pm_commission' );
			$order_platform_commission_total = $order_platform_commission['total'] ?? 0;

			// Create Invoice for Order.
			$order_invoice_args = array(
				'post_type'   => 'gigneo_invoice',
				'post_title'  => sprintf(
				// translators: %s is Order ID
					esc_html__( 'Service order refunded — %s', 'gigneo' ),
					esc_html( gigneo_format_id( $order_id, 'order', false ) )
				),
				'post_status' => 'refunded',
				'post_author' => $employer_user_id,
			);

			$order_invoice_id = wp_insert_post( $order_invoice_args );

			if ( is_wp_error( $order_invoice_id ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
			}

			update_post_meta( $order_id, '_gigneo_pm_refund_invoice_id', $order_invoice_id );

			update_post_meta( $order_invoice_id, '_gigneo_pm_type', 'service_order' );
			update_post_meta( $order_invoice_id, '_gigneo_pm_profile_id', $employer_id );
			update_post_meta( $order_invoice_id, '_gigneo_pm_order_id', $order_id );
			update_post_meta( $order_invoice_id, '_gigneo_pm_total', floatval( $order_total ) );

			// Create Invoice for Commission.
			if ( ! empty( $order_platform_commission_total ) ) {
				$commission_invoice_args = array(
					'post_type'   => 'gigneo_invoice',
					'post_title'  => sprintf(
					// translators: %s is Order ID
						esc_html__( 'Service commission refunded — %s', 'gigneo' ),
						esc_html( gigneo_format_id( $order_id, 'order', false ) )
					),
					'post_status' => 'refunded',
					'post_author' => $employer_user_id,
				);

				$commission_invoice_id = wp_insert_post( $commission_invoice_args );

				if ( is_wp_error( $commission_invoice_id ) ) {
					wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
				}

				update_post_meta( $order_id, '_gigneo_pm_commission_refund_invoice_id', $order_invoice_id );

				update_post_meta( $commission_invoice_id, '_gigneo_pm_type', 'service_commission' );
				update_post_meta( $commission_invoice_id, '_gigneo_pm_profile_id', $employer_id );
				update_post_meta( $commission_invoice_id, '_gigneo_pm_order_id', $order_id );
				update_post_meta( $commission_invoice_id, '_gigneo_pm_total', floatval( $order_platform_commission_total ) );
			}

			// Activity Stage - Order Cancelled.
			update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_cancelled' );

			// Payment Refunded Activity.
			$order_payment_activity = gigneo_trigger_order_activity( $order_id, 'order_payment_refunded', $profile_id );
			if ( is_wp_error( $order_payment_activity ) ) {
				wp_send_json_error( array( 'message' => $order_payment_activity->get_error_message() ), 400 );
			}

			wp_update_post(
				array(
					'ID'          => $order_id,
					'post_status' => 'cancelled',
				)
			);
			$order_cancel_nf_title   = esc_html__( 'Service Order Cancel Request Approved', 'gigneo' );
			$order_cancel_nf_content = sprintf(
			// translators: 1: Service Title, 2: Profile Name
				esc_html__( 'Your service order "%1$s" cancelled request has been approved by %2$s.', 'gigneo' ),
				get_the_title( $service_id ),
				gigneo_profile_name( $profile_id )
			);

			$response['message'] = esc_html__( 'Order cancel request approve successfully.', 'gigneo' );

		} else {

			$order_cancel_reject_activity = gigneo_trigger_order_activity( $order_id, 'order_cancel_request_rejected', $profile_id );
			if ( is_wp_error( $order_cancel_reject_activity ) ) {
				wp_send_json_error( array( 'message' => $order_cancel_reject_activity->get_error_message() ), 400 );
			}

			$cancel_activity_meta           = get_comment_meta( $cancel_request_activity, '_gigneo_cm_cancel_request_info', true );
			$cancel_activity_meta['status'] = 'rejected';

			update_comment_meta( $cancel_request_activity, '_gigneo_cm_cancel_request_info', $cancel_activity_meta );

			delete_post_meta( $order_id, '_gigneo_pm_cancel_request_activity' );

			$order_cancel_nf_title   = esc_html__( 'Order cancel request rejected', 'gigneo' );
			$order_cancel_nf_content = sprintf(
			// translators: 1: Service Title, 2: Profile Name
				esc_html__( 'Your service order "%1$s" cancelled request has been rejected by %2$s.', 'gigneo' ),
				get_the_title( $service_id ),
				gigneo_profile_name( $profile_id )
			);

			$response['message'] = esc_html__( 'Order cancel request rejected successfully.', 'gigneo' );
		}

		// Order Cancel Notification.
		Notifications::trigger_notification( $order_cancel_nf_title, $order_cancel_nf_content, 'order', 'order_cancel_respond', $employer_id, $order_id );

		// Trigger Email.
		$wc_mailer = WC()->mailer();
		$wc_emails = $wc_mailer->get_emails();
		// Email for Employer.
		if ( isset( $wc_emails['Gigneo_Service_Cancel_Response'] ) ) {
			$wc_emails['Gigneo_Service_Cancel_Response']->trigger( $employer_id, $order_id );
		}

		wp_send_json_success( $response, 200 );
	}

	/**
	 * Handle service order review via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function service_order_review() {
		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$profile_id   = gigneo_active_profile_id();
		$profile_type = gigneo_profile_type( $profile_id );
		$current_user = wp_get_current_user();
		$allowed_tags = gigneo_allowed_html_tags();

		if ( empty( $current_user->ID ) || empty( $profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$order_id     = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;
		$order_status = get_post_status( $order_id );

		if ( empty( $order_id ) || 'completed' !== $order_status ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$employer_profile_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$freelancer_profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );

		if ( absint( $profile_id ) !== absint( $freelancer_profile_id ) && absint( $profile_id ) !== absint( $employer_profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		if ( 'employer' === $profile_type ) {

			$communication_level = isset( $_POST['communication_level'] ) ? absint( $_POST['communication_level'] ) : 0;
			$quality_work        = isset( $_POST['quality_work'] ) ? absint( $_POST['quality_work'] ) : 0;
			$understanding       = isset( $_POST['understanding'] ) ? absint( $_POST['understanding'] ) : 0;
			$feedback            = isset( $_POST['review_feedback'] ) ? wp_kses( wp_unslash( $_POST['review_feedback'] ), $allowed_tags ) : '';

			if ( empty( $communication_level ) || empty( $quality_work ) || empty( $understanding ) || empty( $feedback ) ) {
				wp_send_json_error( array( esc_html__( 'Required field could not be empty.', 'gigneo' ) ) );
			}

			$freelancer_review_activity = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_review_activity' );

			if ( ! empty( $freelancer_review_activity ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Already submitted a review.', 'gigneo' ) ), 400 );
			}

			// Service Rating.
			$service_id     = gigneo_post_meta( $order_id, '_gigneo_pm_service_id' );
			$rating_total   = $communication_level + $quality_work + $understanding;
			$rating_average = round( $rating_total / 3, 2 );

			$freelancer_review_meta['_gigneo_cm_review_info'] = array(
				'communication_level' => $communication_level,
				'quality_work'        => $quality_work,
				'understanding'       => $understanding,
				'feedback'            => $feedback,
			);

			$freelancer_review_meta['_gigneo_cm_freelancer_profile_id'] = $freelancer_profile_id;

			$freelancer_review_meta['_gigneo_cm_average_rating'] = $rating_average;

			$order_freelancer_review = gigneo_trigger_order_activity( $order_id, 'freelancer_review', $profile_id, $freelancer_review_meta );
			if ( is_wp_error( $order_freelancer_review ) ) {
				wp_send_json_error( array( 'message' => $order_freelancer_review->get_error_message() ), 400 );
			}

			update_post_meta( $order_id, '_gigneo_pm_freelancer_review_activity', $order_freelancer_review );

			$service_orders_ratings              = gigneo_post_meta( $service_id, '_gigneo_pm_orders_rating' );
			$service_orders_ratings              = ! empty( $service_orders_ratings ) && is_array( $service_orders_ratings ) ? $service_orders_ratings : array();
			$service_orders_ratings[ $order_id ] = $rating_average;

			update_post_meta( $order_id, '_gigneo_pm_average_rating', $rating_average );
			update_post_meta( $service_id, '_gigneo_pm_orders_rating', $service_orders_ratings );

			// Service Average Rating.
			$service_orders_average_rating = gigneo_service_average_rating( $service_id );
			update_post_meta( $service_id, '_gigneo_pm_average_rating', $service_orders_average_rating );

			// Freelancer Rating.
			$freelancer_average_ratings              = gigneo_post_meta( $freelancer_profile_id, '_gigneo_pm_service_orders_rating' );
			$freelancer_average_ratings              = ! empty( $freelancer_average_ratings ) && is_array( $freelancer_average_ratings ) ? $freelancer_average_ratings : array();
			$freelancer_average_ratings[ $order_id ] = $rating_average;
			update_post_meta( $freelancer_profile_id, '_gigneo_pm_service_orders_rating', $freelancer_average_ratings );

			// Freelancer Average Rating.
			$freelancer_average_rating = gigneo_profile_rating( $freelancer_profile_id, 'average' );
			update_post_meta( $freelancer_profile_id, '_gigneo_pm_average_rating', $freelancer_average_rating );

			//newww

			$order_review_nf_title   = esc_html__( 'Order feedback', 'gigneo' );
			$order_review_nf_content = sprintf(
			// translators: 1: Service Title, 2: Profile Name
				esc_html__( 'You have a review on "%1$s" by %2$s.', 'gigneo' ),
				get_the_title( $service_id ),
				gigneo_profile_name( $profile_id )
			);

			Notifications::trigger_notification( $order_review_nf_title, $order_review_nf_content, 'order', 'order_review', $freelancer_profile_id, $order_id );

		} else {

			$communication_level     = isset( $_POST['communication_level'] ) ? absint( $_POST['communication_level'] ) : 0;
			$clarity_of_requirements = isset( $_POST['clarity_of_requirements'] ) ? absint( $_POST['clarity_of_requirements'] ) : 0;
			$professionalism         = isset( $_POST['professionalism'] ) ? absint( $_POST['professionalism'] ) : 0;
			$feedback                = isset( $_POST['review_feedback'] )
				? wp_kses( wp_unslash( $_POST['review_feedback'] ), $allowed_tags )
				: '';

			if (
				empty( $communication_level ) ||
				empty( $clarity_of_requirements ) ||
				empty( $professionalism ) ||
				empty( $feedback )
			) {
				wp_send_json_error(
					array(
						'message' => esc_html__( 'Required field could not be empty.', 'gigneo' ),
					)
				);
			}

			$employer_review_activity = gigneo_post_meta( $order_id, '_gigneo_pm_employer_review_activity' );

			if ( ! empty( $employer_review_activity ) ) {
				wp_send_json_error(
					array(
						'message' => esc_html__( 'Already submitted a review.', 'gigneo' ),
					),
					400
				);
			}

			// Rating calculation.
			$rating_total   = $communication_level + $clarity_of_requirements + $professionalism;
			$rating_average = round( $rating_total / 3, 2 );

			$employer_review_meta = array(
				'_gigneo_cm_employer_review_info' => array(
					'communication_level'     => $communication_level,
					'clarity_of_requirements' => $clarity_of_requirements,
					'professionalism'         => $professionalism,
					'feedback'                => $feedback,
				),
				'_gigneo_cm_employer_profile_id'  => $employer_profile_id,
				'_gigneo_cm_average_rating'       => $rating_average,
			);

			$order_employer_review = gigneo_trigger_order_activity( $order_id, 'employer_review', $profile_id, $employer_review_meta );

			if ( is_wp_error( $order_employer_review ) ) {
				wp_send_json_error( array( 'message' => $order_employer_review->get_error_message() ), 400 );
			}

			update_post_meta( $order_id, '_gigneo_pm_employer_review_activity', $order_employer_review );

			// Store employer ratings per order.
			$employer_ratings              = gigneo_post_meta( $employer_profile_id, '_gigneo_pm_service_orders_rating' );
			$employer_ratings              = is_array( $employer_ratings ) ? $employer_ratings : array();
			$employer_ratings[ $order_id ] = $rating_average;

			update_post_meta( $employer_profile_id, '_gigneo_pm_service_orders_rating', $employer_ratings );

			// Employer average rating.
			$employer_average_rating = gigneo_profile_rating( $employer_profile_id, 'average' );
			update_post_meta( $employer_profile_id, '_gigneo_pm_average_rating', $employer_average_rating );

			$service_id = gigneo_post_meta( $order_id, '_gigneo_pm_service_id' );

			$order_review_nf_title   = esc_html__( 'Order feedback', 'gigneo' );
			$order_review_nf_content = sprintf(
			// translators: 1: Service Title, 2: Profile Name
				esc_html__( 'You have a review on "%1$s" by %2$s.', 'gigneo' ),
				get_the_title( $service_id ),
				gigneo_profile_name( $profile_id )
			);

			Notifications::trigger_notification( $order_review_nf_title, $order_review_nf_content, 'order', 'order_review', $employer_profile_id, $order_id );
		}

		// Trigger Email.
		$wc_mailer = WC()->mailer();
		$wc_emails = $wc_mailer->get_emails();

		$current_profile_id = gigneo_active_profile_id(); // reviewer.
		$order_id           = absint( $order_id );

		if ( isset( $wc_emails['Gigneo_Service_Review'] ) ) {
			$wc_emails['Gigneo_Service_Review']->trigger( $current_profile_id, $order_id );
		}

		wp_send_json_success( array( 'message' => esc_html__( 'Review submitted successfully.', 'gigneo' ) ) );
	}

	/**
	 * Send a message related to an order.
	 *
	 * Handles sending messages between users for a specific order, including validation and saving the message.
	 *
	 * @since 0.0.1
	 */
	public function send_message() {
		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$profile_id   = gigneo_active_profile_id();
		$allowed_tags = gigneo_allowed_html_tags();

		$order_id = isset( $_POST['order_id'] ) ? (int) $_POST['order_id'] : 0;
		$reply_id = isset( $_POST['reply_id'] ) ? (int) $_POST['reply_id'] : 0;

		$order_status = get_post_status( $order_id );

		if ( ! in_array( $order_status, array( 'publish', 'delivered' ), true ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Messages can only be sent for active orders.', 'gigneo' ) ), 403 );
		}

		if ( empty( $profile_id ) || empty( $order_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$employer_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$freelancer_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );

		if ( absint( $profile_id ) !== absint( $freelancer_id ) && absint( $profile_id ) !== absint( $employer_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$profile_type = gigneo_profile_type( $profile_id );

		if ( ! in_array( $profile_type, array( 'freelancer', 'employer' ), true ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$receiver_profile_id = 'freelancer' === $profile_type ? $employer_id : $freelancer_id;

		$order_type = gigneo_post_meta( $order_id, '_gigneo_pm_order_type' );

		if ( 'job' === $order_type ) {
			$job_id        = gigneo_post_meta( $order_id, '_gigneo_pm_job_id' );
			$context_title = get_the_title( $job_id );
		} else {
			$service_id    = gigneo_post_meta( $order_id, '_gigneo_pm_service_id' );
			$context_title = get_the_title( $service_id );
		}

		$message = isset( $_POST['message'] ) ? wp_kses( wp_unslash( $_POST['message'] ), $allowed_tags ) : '';

		$send_message = Messages::send_message( $profile_id, $receiver_profile_id, $message, $reply_id, array(), 'order', $order_id );

		if ( is_wp_error( $send_message ) ) {
			wp_send_json_error( array( 'message' => $send_message->get_error_message() ), 400 );
		}

		// New Chat Message Notification.
		$chat_message_nf_title   = esc_html__( 'Order message', 'gigneo' );
		$chat_message_nf_content = sprintf(
		// translators: 1: Sender Profile Name, 2: Service/Job Title
			esc_html__( '%1$s sent you a new message in order "%2$s".', 'gigneo' ),
			esc_html( gigneo_profile_name( $profile_id ) ),
			esc_html( $context_title )
		);

		Notifications::trigger_notification( $chat_message_nf_title, $chat_message_nf_content, 'order', 'message', $receiver_profile_id, $order_id );

		ob_start();
		gigneo_load_template(
			'dashboard/order/service/parts/detail/messages',
			false,
			array(
				'order_id'   => $order_id,
				'profile_id' => $profile_id,
			)
		);

		$message_body               = ob_get_clean();
		$response['markup']['body'] = $message_body;

		wp_send_json_success( $response, 200 );
	}

	/**
	 * Get messages for a specific order via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function get_order_messages() {

		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$user_id     = gigneo_active_profile_id();
		$order_id    = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;
		$receiver_id = isset( $_POST['receiver_id'] ) ? absint( $_POST['receiver_id'] ) : 0;

		if ( empty( $user_id ) || empty( $order_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$employer_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$freelancer_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );

		if ( absint( $freelancer_id ) !== absint( $user_id ) && absint( $employer_id ) !== absint( $user_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$profile_type = gigneo_profile_type( $user_id );

		if ( ! in_array( $profile_type, array( 'freelancer', 'employer' ), true ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$expected_receiver_id = 'freelancer' === $profile_type ? $employer_id : $freelancer_id;

		if ( $receiver_id && absint( $receiver_id ) !== absint( $expected_receiver_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$receiver_id = absint( $expected_receiver_id );
		global $wpdb;
		$table_messages = $wpdb->prefix . 'gigneo_messages';
		$initial_limit  = gigneo_option( '_gigneo_ts_messages_limit', 30 );
		$offset         = isset( $_POST['offset'] ) ? absint( $_POST['offset'] ) : 0;
		$limit          = isset( $_POST['limit'] ) ? absint( $_POST['limit'] ) : $initial_limit - 5;
		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$total_messages = $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE type = %s AND post_id = %d AND ((sender_id = %d AND receiver_id = %d)OR (sender_id = %d AND receiver_id = %d))', $table_messages, 'order', $order_id, $user_id, $receiver_id, $receiver_id, $user_id ) );
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

		ob_start();
		gigneo_load_template(
			'dashboard/order/service/parts/detail/messages',
			false,
			array(
				'order_id'   => $order_id,
				'profile_id' => $user_id,
				'limit'      => $limit,
				'offset'     => $offset,
			)
		);
		$message_body               = ob_get_clean();
		$response['total']          = $total_messages;
		$response['limit']          = $limit;
		$response['offset']         = $offset;
		$response['markup']['body'] = $message_body;

		wp_send_json_success( $response, 200 );
	}

	/**
	 * Handles service order attachments upload via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function service_order_attachments_upload() {

		// Check nonce.
		$security = isset( $_POST['security'] )
			? sanitize_text_field( wp_unslash( $_POST['security'] ) )
			: '';

		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error(
				array(
					'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ),
				),
				403
			);
		}

		$profile_id   = gigneo_active_profile_id();
		$current_user = wp_get_current_user();

		if ( empty( $current_user->ID ) || empty( $profile_id ) ) {
			wp_send_json_error(
				array(
					'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ),
				),
				403
			);
		}

		$order_id = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;

		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- File uploads validated per-field below (name, tmp_name, size, extension).
		$files = isset( $_FILES['attachments'] ) ? $_FILES['attachments'] : null;

		if ( empty( $files['name'] ) ) {
			wp_send_json_error(
				array(
					'message' => esc_html__( 'Something went wrong.', 'gigneo' ),
				),
				403
			);
		}

		$profile_type          = gigneo_profile_type( $profile_id );
		$service_title         = gigneo_post_meta( $order_id, '_gigneo_pm_service_title' );
		$employer_profile_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$freelancer_profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );

		if ( absint( $profile_id ) !== absint( $employer_profile_id ) &&
			absint( $profile_id ) !== absint( $freelancer_profile_id ) ) {
			wp_send_json_error(
				array(
					'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ),
				),
				403
			);
		}

		$order_status = get_post_status( $order_id );

		if ( ! in_array( $order_status, array( 'publish', 'delivered' ), true ) ) {
			wp_send_json_error(
				array(
					'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ),
				),
				403
			);
		}

		$service_order_extensions = (array) gigneo_option( '_gigneo_ts_service_order_extensions', array( 'jpg', 'jpeg', 'png', 'doc' ) );
		$service_order_max_files  = absint( gigneo_option( '_gigneo_ts_service_order_max_files', 3 ) );
		$service_order_max_size   = absint( gigneo_option( '_gigneo_ts_service_order_max_size', 1 ) );
		$max_file_size            = $service_order_max_size * 1024 * 1024;

		$files_collection = array();

		if ( $files && is_array( $files['name'] ) ) {

			$total_files = count( $files['name'] );

			if ( $total_files > $service_order_max_files ) {
				wp_send_json_error(
					array(
						'message' => sprintf(
							// translators: %s is the max files.
							esc_html__( 'You can only upload up to %s files.', 'gigneo' ),
							$service_order_max_files
						),
					),
					403
				);
			}

			for ( $i = 0; $i < $total_files; $i++ ) {

				$error    = isset( $files['error'][ $i ] ) ? absint( $files['error'][ $i ] ) : UPLOAD_ERR_NO_FILE;
				$size     = isset( $files['size'][ $i ] ) ? absint( $files['size'][ $i ] ) : 0;
				$name     = isset( $files['name'][ $i ] ) ? sanitize_file_name( wp_unslash( $files['name'][ $i ] ) ) : '';
				$tmp_name = isset( $files['tmp_name'][ $i ] ) ? sanitize_text_field( $files['tmp_name'][ $i ] ) : '';

				$ext = strtolower( pathinfo( $name, PATHINFO_EXTENSION ) );

				if ( UPLOAD_ERR_OK !== $error ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
								// translators: %s is the file name.
								esc_html__( 'Error uploading file: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				if ( empty( $tmp_name ) || ! is_uploaded_file( $tmp_name ) ) {
					wp_send_json_error(
						array(
							'message' => esc_html__( 'Invalid uploaded file.', 'gigneo' ),
						),
						403
					);
				}

				if ( ! in_array( $ext, $service_order_extensions, true ) ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
								// translators: %s is the file name.
								esc_html__( 'File type not allowed: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				$filetype = wp_check_filetype_and_ext( $tmp_name, $name );

				if ( empty( $filetype['ext'] ) || empty( $filetype['type'] ) || $filetype['ext'] !== $ext ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
								// translators: %s is the file name.
								esc_html__( 'File type not allowed: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				if ( $size > $max_file_size ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
								// translators: %1$s is file name, %2$s is maximum file size in MB.
								esc_html__( '%1$s exceeds the maximum file size of %2$s MB.', 'gigneo' ),
								esc_html( $name ),
								esc_html( $service_order_max_size )
							),
						),
						403
					);
				}

				$files_collection[] = array(
					'name'     => $name,
					'tmp_name' => $tmp_name,
					'size'     => $size,
				);
			}

			if ( empty( $files_collection ) ) {
				wp_send_json_error(
					array(
						'message' => esc_html__( 'Something went wrong.', 'gigneo' ),
					),
					403
				);
			}

			global $wpdb;

			$table_attachments = $wpdb->prefix . 'gigneo_attachments';

			$service_order_dir = gigneo_upload_dir_path(
				'/' . gmdate( 'Y' ) . '/' . gmdate( 'm' ) . '/order/' . $order_id
			);

			if ( ! file_exists( $service_order_dir ) ) {
				wp_mkdir_p( $service_order_dir );
			}

			$upload_base_path = gigneo_upload_dir_path();
			$upload_base_url  = gigneo_upload_dir_url();

			foreach ( $files_collection as $file ) {

				$name     = $file['name'];
				$tmp_name = $file['tmp_name'];
				$size     = absint( $file['size'] );

				$file_ext      = strtolower( pathinfo( $name, PATHINFO_EXTENSION ) );
				$random_number = wp_rand( 1000, 9999 );
				$base_name     = sanitize_file_name( pathinfo( $name, PATHINFO_FILENAME ) );
				$file_name     = $base_name . '-' . $random_number . '.' . $file_ext;
				$file_name     = wp_unique_filename( $service_order_dir, $file_name );
				$file_path     = trailingslashit( $service_order_dir ) . $file_name;

				if ( is_uploaded_file( $tmp_name ) && gigneo_move_uploaded_file( $tmp_name, $file_path ) ) {

					$relative_path = str_replace( $upload_base_path, '', $file_path );
					$file_url      = str_replace( $upload_base_path, $upload_base_url, $file_path );

					$meta = array(
						'type' => 'upload',
					);
					// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
					$insert_file = $wpdb->insert(
						$table_attachments,
						array(
							'post_id'         => absint( $order_id ),
							'profile_id'      => absint( $profile_id ),
							'name'            => $name,
							'mime_type'       => $file_ext,
							'size'            => $size,
							'guid'            => esc_url_raw( $file_url ),
							'path'            => sanitize_text_field( $relative_path ),
							'post_type'       => 'order',
							'meta'            => maybe_serialize( $meta ),
							'uploaded_at'     => current_time( 'mysql' ),
							'uploaded_at_gmt' => current_time( 'mysql', true ),
						),
						array( '%d', '%d', '%s', '%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s' )
					);
					// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

					if ( false === $insert_file ) {
						wp_send_json_error(
							array(
								'message' => esc_html__( 'Something went wrong.', 'gigneo' ),
							),
							403
						);
					}
				}
			}
		}

		// Activity log
		$order_attachment_activity = gigneo_trigger_order_activity( $order_id, 'attachment_upload' );

		if ( is_wp_error( $order_attachment_activity ) ) {
			wp_send_json_error(
				array(
					'message' => $order_attachment_activity->get_error_message(),
				),
				400
			);
		}

		$attachment_uploaded_nf_title = esc_html__( 'Attachment uploaded', 'gigneo' );

		$attachment_uploaded_nf_content = sprintf(
		// translators: 1: Service title, 2: Profile name.
			esc_html__( 'A new attachment has been uploaded for "%1$s" by %2$s.', 'gigneo' ),
			esc_html( $service_title ),
			esc_html( gigneo_profile_name( $profile_id ) )
		);

		Notifications::trigger_notification(
			$attachment_uploaded_nf_title,
			$attachment_uploaded_nf_content,
			'order',
			'attachment_uploaded',
			( 'freelancer' === $profile_type ) ? $employer_profile_id : $freelancer_profile_id,
			$order_id
		);

		wp_send_json_success(
			array(
				'message' => esc_html__( 'Attachment uploaded successfully.', 'gigneo' ),
			),
			200
		);
	}

	/**
	 * Handle job order delivery via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function job_order_deliver() {
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$profile_id   = gigneo_active_profile_id();
		$profile_type = gigneo_profile_type( $profile_id );
		$current_user = wp_get_current_user();

		if ( empty( $current_user->ID ) || empty( $profile_id ) || 'freelancer' !== $profile_type ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$allowed_tags = gigneo_allowed_html_tags();

		$order_id    = isset( $_POST['order_id'] ) ? absint( wp_unslash( $_POST['order_id'] ) ) : 0;
		$type        = ! empty( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : '';
		$description = ! empty( $_POST['description'] ) ? wp_kses( wp_unslash( $_POST['description'] ), $allowed_tags ) : '';

		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- File uploads validated per-field below (name, tmp_name, size, extension).
		$files = isset( $_FILES['attachments'] ) ? $_FILES['attachments'] : array();

		if ( empty( $order_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
		}

		if ( empty( $type ) || empty( $description ) || empty( $files['name'] ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Required field could not be empty.', 'gigneo' ) ), 403 );
		}

		$job_title             = gigneo_post_meta( $order_id, '_gigneo_pm_job_title' );
		$freelancer_profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		$employer_profile_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );

		if ( empty( $freelancer_profile_id ) || absint( $profile_id ) !== absint( $freelancer_profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$final_delivery_activity = gigneo_post_meta( $order_id, '_gigneo_pm_delivery_activity' );

		if ( ! empty( $final_delivery_activity ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Cannot make another delivery until final delivery response.', 'gigneo' ) ), 403 );
		}

		$job_order_extensions = gigneo_option( '_gigneo_ts_job_order_extensions', array( 'jpg', 'jpeg', 'png', 'doc' ) );
		$job_order_max_files  = gigneo_option( '_gigneo_ts_job_order_max_files', '3' );
		$job_order_max_size   = gigneo_option( '_gigneo_ts_job_order_max_size', '1' );
		$max_file_size        = $job_order_max_size * 1024 * 1024;

		$files_collection = array();
		$files_uploaded   = array();

		if ( $files && is_array( $files['name'] ) ) {
			$total_files = count( $files['name'] );

			if ( $total_files > $job_order_max_files ) {
				wp_send_json_error(
					array(
						'message' => sprintf(
						// translators: %s is max files
							esc_html__( 'You can only upload up to %s files.', 'gigneo' ),
							esc_html( $job_order_max_files )
						),
					),
					403
				);
			}

			for ( $i = 0; $i < $total_files; $i++ ) {

				$error = isset( $files['error'][ $i ] )
					? absint( $files['error'][ $i ] )
					: UPLOAD_ERR_NO_FILE;

				$size = isset( $files['size'][ $i ] )
					? absint( $files['size'][ $i ] )
					: 0;

				$name = isset( $files['name'][ $i ] )
					? sanitize_file_name( wp_unslash( $files['name'][ $i ] ) )
					: '';

				$tmp_name = isset( $files['tmp_name'][ $i ] )
					? sanitize_text_field( $files['tmp_name'][ $i ] )
					: '';

				$ext = strtolower( pathinfo( $name, PATHINFO_EXTENSION ) );

				// Check upload errors.
				if ( UPLOAD_ERR_OK !== $error ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: %s: File name. */
								esc_html__( 'Error uploading file: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				// Validate uploaded file.
				if ( empty( $tmp_name ) || ! is_uploaded_file( $tmp_name ) ) {
					wp_send_json_error(
						array(
							'message' => esc_html__( 'Invalid uploaded file.', 'gigneo' ),
						),
						403
					);
				}

				// Validate file extension.
				if ( ! in_array( $ext, $job_order_extensions, true ) ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: %s: File name. */
								esc_html__( 'File type not allowed: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				$filetype = wp_check_filetype_and_ext( $tmp_name, $name );

				if ( empty( $filetype['ext'] ) || empty( $filetype['type'] ) || $filetype['ext'] !== $ext ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: %s: File name. */
								esc_html__( 'File type not allowed: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				// Validate file size.
				if ( $size > $max_file_size ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: 1: File name, 2: Maximum file size in MB. */
								esc_html__( '%1$s exceeds the maximum file size of %2$s MB.', 'gigneo' ),
								esc_html( $name ),
								esc_html( $job_order_max_size )
							),
						),
						403
					);
				}

				$files_collection[] = array(
					'name'     => $name,
					'tmp_name' => $tmp_name,
					'size'     => $size,
					'ext'      => sanitize_key( $ext ),
				);
			}

			if ( empty( $files_collection ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
			}

			global $wpdb;
			$table_attachments = $wpdb->prefix . 'gigneo_attachments';
			$job_order_dir     = gigneo_upload_dir_path( '/' . gmdate( 'Y' ) . '/' . gmdate( 'm' ) . '/order/' . $order_id );

			if ( ! file_exists( $job_order_dir ) ) {
				wp_mkdir_p( $job_order_dir );
			}

			foreach ( $files_collection as $file ) {
				$name          = $file['name'];
				$tmp_name      = $file['tmp_name'];
				$size          = $file['size'];
				$file_ext      = pathinfo( $name, PATHINFO_EXTENSION );
				$random_number = wp_rand( 1000, 9999 );
				$file_name     = pathinfo( sanitize_file_name( $name ), PATHINFO_FILENAME ) . '-' . $random_number . '.' . $file_ext;
				$file_name     = wp_unique_filename( $job_order_dir, $file_name );
				$file_path     = $job_order_dir . '/' . $file_name;

				if ( is_uploaded_file( $tmp_name ) && gigneo_move_uploaded_file( $tmp_name, $file_path ) ) {
					$path = str_replace( gigneo_upload_dir_path(), '', $file_path );
					$url  = str_replace( gigneo_upload_dir_path(), gigneo_upload_dir_url(), $file_path );
					$meta = array( 'type' => 'deliverable' );

					// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
					$wpdb->insert(
						$table_attachments,
						array(
							'post_id'         => absint( $order_id ),
							'profile_id'      => absint( $profile_id ),
							'name'            => $name,
							'mime_type'       => $file_ext,
							'size'            => $size,
							'guid'            => $url,
							'path'            => $path,
							'post_type'       => 'order',
							'meta'            => maybe_serialize( $meta ),
							'uploaded_at'     => current_time( 'mysql' ),
							'uploaded_at_gmt' => current_time( 'mysql', 1 ),
						),
						array( '%d', '%d', '%s', '%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s' )
					);
					// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

					$files_uploaded[] = $wpdb->insert_id;
				}
			}
		}

		$order_deliver_activity_meta['_gigneo_cm_order_delivery_info'] = array(
			'status'      => 'pending',
			'description' => $description,
			'attachments' => $files_uploaded,
		);

		if ( 'final' === $type ) {
			$current_stage     = gigneo_post_meta( $order_id, '_gigneo_pm_activity_stage' );
			$revision_activity = gigneo_post_meta( $order_id, '_gigneo_pm_revision_activity' );

			$order_delivered_activity = gigneo_trigger_order_activity( $order_id, 'order_final_delivery', $profile_id, $order_deliver_activity_meta );
			if ( is_wp_error( $order_delivered_activity ) ) {
				wp_send_json_error( array( 'message' => $order_delivered_activity->get_error_message() ), 400 );
			}

			if ( 'order_revision_requested' === $current_stage && ! empty( $revision_activity ) ) {
				$revision_delivery_info           = get_comment_meta( $revision_activity, '_gigneo_cm_revision_info', true );
				$revision_delivery_info['status'] = 'delivered';
				update_comment_meta( $revision_activity, '_gigneo_cm_revision_info', $revision_delivery_info );
				update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_revision_delivered' );
				delete_post_meta( $order_id, '_gigneo_pm_revision_activity' );
			} else {
				update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_delivered' );
			}

			update_post_meta( $order_id, '_gigneo_pm_delivery_activity', $order_delivered_activity );

			wp_update_post(
				array(
					'ID'          => $order_id,
					'post_status' => 'delivered',
				)
			);

			$final_delivery_nf_title   = esc_html__( 'Final delivery', 'gigneo' );
			$final_delivery_nf_content = sprintf(
				// translators: 1: Job title 2: Profile name
				esc_html__( 'The final delivery for the job order "%1$s" has been submitted by %2$s.', 'gigneo' ),
				esc_html( $job_title ),
				esc_html( gigneo_profile_name( $profile_id ) )
			);

			// Trigger Email.
			$wc_mailer = WC()->mailer();
			$wc_emails = $wc_mailer->get_emails();
			// Email for Employer.
			if ( isset( $wc_emails['Gigneo_Job_Delivered'] ) ) {
				$wc_emails['Gigneo_Job_Delivered']->trigger( $employer_profile_id, $order_id );
			}

			Notifications::trigger_notification( $final_delivery_nf_title, $final_delivery_nf_content, 'order', 'final_delivery', $employer_profile_id, $order_id );

		} else {
			$order_delivered_activity = gigneo_trigger_order_activity( $order_id, 'order_draft_delivery', $profile_id, $order_deliver_activity_meta );
			if ( is_wp_error( $order_delivered_activity ) ) {
				wp_send_json_error( array( 'message' => $order_delivered_activity->get_error_message() ), 400 );
			}

			$draft_delivery_nf_title   = esc_html__( 'Draft delivery', 'gigneo' );
			$draft_delivery_nf_content = sprintf(
			// translators: 1: Job title 2: Profile name
				esc_html__( 'The draft delivery for the job order "%1$s" has been submitted by %2$s.', 'gigneo' ),
				esc_html( $job_title ),
				esc_html( gigneo_profile_name( $profile_id ) )
			);
			Notifications::trigger_notification( $draft_delivery_nf_title, $draft_delivery_nf_content, 'order', 'draft_delivery', $employer_profile_id, $order_id );
		}

		wp_send_json_success( array( 'message' => esc_html__( 'Order delivered successfully.', 'gigneo' ) ) );
	}

	/**
	 * Handle job order revision via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function job_order_revision() {
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$profile_id   = gigneo_active_profile_id();
		$profile_type = gigneo_profile_type( $profile_id );
		$current_user = wp_get_current_user();

		if ( empty( $current_user->ID ) || empty( $profile_id ) || 'employer' !== $profile_type ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$allowed_tags = gigneo_allowed_html_tags();

		$order_id    = isset( $_POST['order_id'] ) ? absint( wp_unslash( $_POST['order_id'] ) ) : 0;
		$description = ! empty( $_POST['description'] ) ? wp_kses( wp_unslash( $_POST['description'] ), $allowed_tags ) : '';

		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- File uploads validated per-field below (name, tmp_name, size, extension).
		$files = isset( $_FILES['attachments'] ) ? $_FILES['attachments'] : array();

		if ( empty( $order_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
		}

		if ( empty( $description ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Required field could not be empty.', 'gigneo' ) ), 403 );
		}

		$employer_profile_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$freelancer_profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );

		if ( empty( $employer_profile_id ) || absint( $profile_id ) !== absint( $employer_profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$current_stage        = gigneo_post_meta( $order_id, '_gigneo_pm_activity_stage' );
		$delivery_activity_id = gigneo_post_meta( $order_id, '_gigneo_pm_delivery_activity' );

		if ( ( 'order_delivered' !== $current_stage && 'order_revision_delivered' !== $current_stage ) || empty( $delivery_activity_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
		}

		$revision_delivery_activity = gigneo_post_meta( $order_id, '_gigneo_pm_revision_activity' );
		if ( ! empty( $revision_delivery_activity ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Already revision requested.', 'gigneo' ) ), 403 );
		}

		$job_title      = gigneo_post_meta( $order_id, '_gigneo_pm_job_title' );
		$price_plan     = gigneo_post_meta( $order_id, '_gigneo_pm_price_plan' );
		$total_revision = ! empty( $price_plan['revisions'] ) ? $price_plan['revisions'] : 'unlimited';
		$revision_used  = gigneo_post_meta( $order_id, '_gigneo_pm_revision_used' );
		$revision_used  = ! empty( $revision_used ) ? intval( $revision_used ) : 0;
		$revision_left  = is_numeric( $total_revision ) ? intval( $total_revision ) - $revision_used : 0;

		if ( 'unlimited' !== $total_revision && $revision_left < 1 ) {
			wp_send_json_error( array( 'message' => esc_html__( 'No revisions left.', 'gigneo' ) ), 403 );
		}

		$job_order_extensions = gigneo_option( '_gigneo_ts_job_order_extensions', array( 'jpg', 'jpeg', 'png', 'doc' ) );
		$job_order_max_files  = gigneo_option( '_gigneo_ts_job_order_max_files', '3' );
		$job_order_max_size   = gigneo_option( '_gigneo_ts_job_order_max_size', '1' );
		$max_file_size        = $job_order_max_size * 1024 * 1024;

		$files_collection = array();
		$files_uploaded   = array();

		if ( $files && is_array( $files['name'] ) ) {
			$total_files = count( $files['name'] );

			if ( $total_files > $job_order_max_files ) {
				wp_send_json_error(
					array(
						'message' => sprintf(
						// translators: %s max files
							esc_html__( 'You can only upload up to %s files.', 'gigneo' ),
							esc_html( $job_order_max_files )
						),
					),
					403
				);
			}

			for ( $i = 0; $i < $total_files; $i++ ) {

				$error = isset( $files['error'][ $i ] )
					? absint( $files['error'][ $i ] )
					: UPLOAD_ERR_NO_FILE;

				$size = isset( $files['size'][ $i ] )
					? absint( $files['size'][ $i ] )
					: 0;

				$name = isset( $files['name'][ $i ] )
					? sanitize_file_name( wp_unslash( $files['name'][ $i ] ) )
					: '';

				$tmp_name = isset( $files['tmp_name'][ $i ] )
					? sanitize_text_field( $files['tmp_name'][ $i ] )
					: '';

				$ext = strtolower( pathinfo( $name, PATHINFO_EXTENSION ) );

				// Check upload errors.
				if ( UPLOAD_ERR_OK !== $error ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: %s: File name. */
								esc_html__( 'Error uploading file: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				// Validate uploaded file.
				if ( empty( $tmp_name ) || ! is_uploaded_file( $tmp_name ) ) {
					wp_send_json_error(
						array(
							'message' => esc_html__( 'Invalid uploaded file.', 'gigneo' ),
						),
						403
					);
				}

				// Validate file extension.
				if ( ! in_array( $ext, $job_order_extensions, true ) ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: %s: File name. */
								esc_html__( 'File type not allowed: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				$filetype = wp_check_filetype_and_ext( $tmp_name, $name );

				if ( empty( $filetype['ext'] ) || empty( $filetype['type'] ) || $filetype['ext'] !== $ext ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: %s: File name. */
								esc_html__( 'File type not allowed: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				// Validate file size.
				if ( $size > $max_file_size ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: 1: File name, 2: Maximum file size in MB. */
								esc_html__( '%1$s exceeds the maximum file size of %2$s MB.', 'gigneo' ),
								esc_html( $name ),
								esc_html( $job_order_max_size )
							),
						),
						403
					);
				}

				$files_collection[] = array(
					'name'     => $name,
					'tmp_name' => $tmp_name,
					'size'     => $size,
					'ext'      => sanitize_key( $ext ),
				);
			}

			if ( empty( $files_collection ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
			}

			global $wpdb;
			$table_attachments = $wpdb->prefix . 'gigneo_attachments';
			$job_order_dir     = gigneo_upload_dir_path( '/' . gmdate( 'Y' ) . '/' . gmdate( 'm' ) . '/order/' . $order_id );

			if ( ! file_exists( $job_order_dir ) ) {
				wp_mkdir_p( $job_order_dir );
			}

			foreach ( $files_collection as $file ) {
				$name          = $file['name'];
				$tmp_name      = $file['tmp_name'];
				$size          = $file['size'];
				$file_ext      = pathinfo( $name, PATHINFO_EXTENSION );
				$random_number = wp_rand( 1000, 9999 );
				$file_name     = pathinfo( sanitize_file_name( $name ), PATHINFO_FILENAME ) . '-' . $random_number . '.' . $file_ext;
				$file_name     = wp_unique_filename( $job_order_dir, $file_name );
				$file_path     = $job_order_dir . '/' . $file_name;

				if ( is_uploaded_file( $tmp_name ) && gigneo_move_uploaded_file( $tmp_name, $file_path ) ) {
					$path = str_replace( gigneo_upload_dir_path(), '', $file_path );
					$url  = str_replace( gigneo_upload_dir_path(), gigneo_upload_dir_url(), $file_path );
					$meta = array( 'type' => 'revision' );

					// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
					$wpdb->insert(
						$table_attachments,
						array(
							'post_id'         => absint( $order_id ),
							'profile_id'      => absint( $profile_id ),
							'name'            => $name,
							'mime_type'       => $file_ext,
							'size'            => $size,
							'guid'            => $url,
							'path'            => $path,
							'post_type'       => 'order',
							'meta'            => maybe_serialize( $meta ),
							'uploaded_at'     => current_time( 'mysql' ),
							'uploaded_at_gmt' => current_time( 'mysql', 1 ),
						),
						array( '%d', '%d', '%s', '%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s' )
					);
					// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

					$files_uploaded[] = $wpdb->insert_id;
				}
			}
		}

		$order_revision_activity_meta                                     = array();
		$order_revision_activity_meta['_gigneo_cm_revision_info']         = array(
			'status'      => 'requested',
			'description' => $description,
			'attachments' => $files_uploaded,
		);
		$order_revision_activity_meta['_gigneo_cm_order_revision_status'] = 'pending';

		$order_revision_activity = gigneo_trigger_order_activity( $order_id, 'order_revision_requested', $profile_id, $order_revision_activity_meta );
		if ( is_wp_error( $order_revision_activity ) ) {
			wp_send_json_error( array( 'message' => $order_revision_activity->get_error_message() ), 400 );
		}

		$order_deliver_activity_meta           = get_comment_meta( $delivery_activity_id, '_gigneo_cm_order_delivery_info', true );
		$order_deliver_activity_meta['status'] = 'revision';
		update_comment_meta( $delivery_activity_id, '_gigneo_cm_order_delivery_info', $order_deliver_activity_meta );

		update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_revision_requested' );
		update_post_meta( $order_id, '_gigneo_pm_revision_activity', $order_revision_activity );
		update_post_meta( $order_id, '_gigneo_pm_revision_used', $revision_used + 1 );
		delete_post_meta( $order_id, '_gigneo_pm_delivery_activity' );

		wp_update_post(
			array(
				'ID'          => $order_id,
				'post_status' => 'publish',
			)
		);

		$revision_nf_title   = esc_html__( 'Revision requested', 'gigneo' );
		$revision_nf_content = sprintf(
		// translators: 1: Job title 2: Profile name
			esc_html__( 'Revision requested for the job order "%1$s" by %2$s.', 'gigneo' ),
			esc_html( $job_title ),
			esc_html( gigneo_profile_name( $profile_id ) )
		);
		Notifications::trigger_notification( $revision_nf_title, $revision_nf_content, 'order', 'revision_requested', $freelancer_profile_id, $order_id );

		// Trigger Email.
		$wc_mailer = WC()->mailer();
		$wc_emails = $wc_mailer->get_emails();
		// Email for Freelancer.
		if ( isset( $wc_emails['Gigneo_Job_Revision'] ) ) {
			$wc_emails['Gigneo_Job_Revision']->trigger( $freelancer_profile_id, $order_id );
		}

		wp_send_json_success( array( 'message' => esc_html__( 'Revision requested successfully.', 'gigneo' ) ) );
	}

	/**
	 * Approve job order delivery via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function job_order_delivery_approve() {
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$profile_id   = gigneo_active_profile_id();
		$profile_type = gigneo_profile_type( $profile_id );
		$current_user = wp_get_current_user();

		if ( empty( $current_user->ID ) || empty( $profile_id ) || 'employer' !== $profile_type ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$order_id     = isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0;
		$order_status = get_post_status( $order_id );

		if ( empty( $order_id ) || 'delivered' !== $order_status ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$employer_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$freelancer_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );

		if ( absint( $employer_id ) !== absint( $profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$current_stage        = gigneo_post_meta( $order_id, '_gigneo_pm_activity_stage' );
		$delivery_activity_id = gigneo_post_meta( $order_id, '_gigneo_pm_delivery_activity' );

		if ( ( 'order_delivered' !== $current_stage && 'order_revision_delivered' !== $current_stage ) || empty( $delivery_activity_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
		}

		$order_delivery_approved_activity = gigneo_trigger_order_activity( $order_id, 'order_delivery_approved', $profile_id );
		if ( is_wp_error( $order_delivery_approved_activity ) ) {
			wp_send_json_error( array( 'message' => $order_delivery_approved_activity->get_error_message() ), 400 );
		}

		$order_complete_activity = gigneo_trigger_order_activity( $order_id, 'order_completed', $profile_id );
		if ( is_wp_error( $order_complete_activity ) ) {
			wp_send_json_error( array( 'message' => $order_complete_activity->get_error_message() ), 400 );
		}

		$order_deliver_activity_meta           = get_comment_meta( $delivery_activity_id, '_gigneo_cm_order_delivery_info', true );
		$order_deliver_activity_meta['status'] = 'approved';
		update_comment_meta( $delivery_activity_id, '_gigneo_cm_order_delivery_info', $order_deliver_activity_meta );

		update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_completed' );

		$freelancer_user_id              = get_post_field( 'post_author', $freelancer_id );
		$order_total                     = gigneo_post_meta( $order_id, '_gigneo_pm_subtotal' );
		$order_platform_commission       = gigneo_commission( $order_total, 'job', $freelancer_id );
		$order_platform_commission_total = $order_platform_commission['total'] ?? 0;

		$order_invoice_args = array(
			'post_type'   => 'gigneo_invoice',
			'post_title'  => sprintf(
			// translators: %s is an order ID.
				esc_html__(
					'Job order completed — %s',
					'gigneo'
				),
				esc_html( gigneo_format_id( $order_id, 'order', false ) )
			),
			'post_status' => 'completed',
			'post_author' => $freelancer_user_id,
		);
		$order_invoice_id   = wp_insert_post( $order_invoice_args );

		if ( is_wp_error( $order_invoice_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
		}

		update_post_meta( $order_id, '_gigneo_pm_freelancer_invoice_id', $order_invoice_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_type', 'job_order' );
		update_post_meta( $order_invoice_id, '_gigneo_pm_profile_id', $freelancer_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_order_id', $order_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_total', floatval( $order_total ) );

		if ( ! empty( $order_platform_commission_total ) ) {
			$commission_invoice_args = array(
				'post_type'   => 'gigneo_invoice',
				'post_title'  => sprintf(
				// translators: %s is an order ID.
					esc_html__( 'Job commission — %s', 'gigneo' ),
					esc_html( gigneo_format_id( $order_id, 'order', false ) )
				),
				'post_status' => 'completed',
				'post_author' => $freelancer_user_id,
			);
			$commission_invoice_id   = wp_insert_post( $commission_invoice_args );

			if ( ! is_wp_error( $commission_invoice_id ) ) {
				update_post_meta( $order_id, '_gigneo_pm_commission_invoice_id', $commission_invoice_id );
				update_post_meta( $commission_invoice_id, '_gigneo_pm_type', 'job_commission' );
				update_post_meta( $commission_invoice_id, '_gigneo_pm_profile_id', $freelancer_id );
				update_post_meta( $commission_invoice_id, '_gigneo_pm_order_id', $order_id );
				update_post_meta( $commission_invoice_id, '_gigneo_pm_total', -floatval( $order_platform_commission_total ) );
			}
		}

		gigneo_trigger_order_activity( $order_id, 'order_payment_released', $profile_id );

		wp_update_post(
			array(
				'ID'          => $order_id,
				'post_status' => 'completed',
			)
		);
		update_post_meta( $order_id, '_gigneo_pm_completed_at', current_time( 'mysql' ) );
		update_post_meta( $order_id, '_gigneo_pm_completed_at_gmt', current_time( 'mysql', 1 ) );

		$job_id                = gigneo_post_meta( $order_id, '_gigneo_pm_job_id' );
		$freelancer_profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		wp_update_post(
			array(
				'ID'          => $job_id,
				'post_status' => 'completed',
			)
		);

		$completed_orders_freelancer = gigneo_profile_stat( 'completed_orders_count', $freelancer_profile_id );
		$completed_orders_employer   = gigneo_profile_stat( 'completed_orders_count', $profile_id );

		update_post_meta( $freelancer_profile_id, '_gigneo_pm_orders_completed', intval( $completed_orders_freelancer['lifetime'] ) );
		update_post_meta( $profile_id, '_gigneo_pm_orders_completed', intval( $completed_orders_employer['lifetime'] ) );

		$order_approve_nf_title   = esc_html__( 'Delivery approved', 'gigneo' );
		$order_approve_nf_content = sprintf(
		// translators: 1: Job title 2: Profile name
			esc_html__( 'Your job order delivery "%1$s" has been approved by %2$s.', 'gigneo' ),
			get_the_title( $job_id ),
			gigneo_profile_name( $profile_id )
		);
		Notifications::trigger_notification( $order_approve_nf_title, $order_approve_nf_content, 'order', 'order_delivery_approve', $freelancer_profile_id, $order_id );

		$order_complete_nf_title   = esc_html__( 'Order completed', 'gigneo' );
		$order_complete_nf_content = sprintf(
		// translators: 1: Job title 2: Profile name
			esc_html__( 'The job order "%1$s" has been completed by %2$s.', 'gigneo' ),
			get_the_title( $job_id ),
			esc_html( gigneo_profile_name( $profile_id ) )
		);
		Notifications::trigger_notification( $order_complete_nf_title, $order_complete_nf_content, 'order', 'order_completed', $freelancer_id, $order_id );

		// Trigger Email.
		$wc_mailer             = WC()->mailer();
		$wc_emails             = $wc_mailer->get_emails();
		$freelancer_profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		$freelancer_user_id    = get_post_field( 'post_author', $freelancer_profile_id );
		// Email for Freelancer.
		if ( isset( $wc_emails['Gigneo_Job_Completed'] ) ) {
			$wc_emails['Gigneo_Job_Completed']->trigger( $freelancer_user_id, $order_id );
		}

		wp_send_json_success( array( 'message' => esc_html__( 'Order delivery approved successfully.', 'gigneo' ) ), 200 );
	}

	/**
	 * Job order complete via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function job_order_complete() {
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$profile_id   = gigneo_active_profile_id();
		$profile_type = gigneo_profile_type( $profile_id );
		$current_user = wp_get_current_user();

		if ( empty( $current_user->ID ) || empty( $profile_id ) || 'employer' !== $profile_type ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$order_id     = isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0;
		$order_status = get_post_status( $order_id );

		if ( empty( $order_id ) || ! in_array( $order_status, array( 'publish', 'delivered' ), true ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$job_title     = gigneo_post_meta( $order_id, '_gigneo_pm_job_title' );
		$employer_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$freelancer_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );

		if ( absint( $employer_id ) !== absint( $profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$order_complete_activity = gigneo_trigger_order_activity( $order_id, 'order_completed', $profile_id );
		if ( is_wp_error( $order_complete_activity ) ) {
			wp_send_json_error( array( 'message' => $order_complete_activity->get_error_message() ), 400 );
		}

		update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_completed' );

		$freelancer_user_id              = get_post_field( 'post_author', $freelancer_id );
		$order_total                     = gigneo_post_meta( $order_id, '_gigneo_pm_subtotal' );
		$order_platform_commission       = gigneo_commission( $order_total, 'job', $freelancer_id );
		$order_platform_commission_total = $order_platform_commission['total'] ?? 0;
		$job_id                          = gigneo_post_meta( $order_id, '_gigneo_pm_job_id' );

		$order_invoice_args = array(
			'post_type'   => 'gigneo_invoice',
			'post_title'  => sprintf(
			// translators: %s is an order id
				esc_html__( 'Job Order Completed — %s', 'gigneo' ),
				esc_html( gigneo_format_id( $order_id, 'order', false ) )
			),
			'post_status' => 'completed',
			'post_author' => $freelancer_user_id,
		);
		$order_invoice_id   = wp_insert_post( $order_invoice_args );

		if ( ! is_wp_error( $order_invoice_id ) ) {
			update_post_meta( $order_id, '_gigneo_pm_freelancer_invoice_id', $order_invoice_id );
			update_post_meta( $order_invoice_id, '_gigneo_pm_type', 'job_order' );
			update_post_meta( $order_invoice_id, '_gigneo_pm_profile_id', $freelancer_id );
			update_post_meta( $order_invoice_id, '_gigneo_pm_order_id', $order_id );
			update_post_meta( $order_invoice_id, '_gigneo_pm_total', floatval( $order_total ) );
		}

		if ( ! empty( $order_platform_commission_total ) ) {
			$commission_invoice_args = array(
				'post_type'   => 'gigneo_invoice',
				'post_title'  => sprintf(
				// translators: %s is an order id
					esc_html__( 'Job Commission — #%s', 'gigneo' ),
					esc_html( gigneo_format_id( $order_id, 'order', false ) )
				),
				'post_status' => 'paid',
				'post_author' => $freelancer_user_id,
			);
			$commission_invoice_id   = wp_insert_post( $commission_invoice_args );

			if ( ! is_wp_error( $commission_invoice_id ) ) {
				update_post_meta( $order_id, '_gigneo_pm_commission_invoice_id', $commission_invoice_id );
				update_post_meta( $commission_invoice_id, '_gigneo_pm_type', 'job_commission' );
				update_post_meta( $commission_invoice_id, '_gigneo_pm_profile_id', $freelancer_id );
				update_post_meta( $commission_invoice_id, '_gigneo_pm_order_id', $order_id );
				update_post_meta( $commission_invoice_id, '_gigneo_pm_total', -floatval( $order_platform_commission_total ) );
			}
		}

		gigneo_trigger_order_activity( $order_id, 'order_payment_released', $profile_id );

		wp_update_post(
			array(
				'ID'          => $order_id,
				'post_status' => 'completed',
			)
		);
		wp_update_post(
			array(
				'ID'          => $job_id,
				'post_status' => 'completed',
			)
		);
		update_post_meta( $order_id, '_gigneo_pm_completed_at', current_time( 'mysql' ) );
		update_post_meta( $order_id, '_gigneo_pm_completed_at_gmt', current_time( 'mysql', 1 ) );

		$freelancer_profile_id       = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		$completed_orders_freelancer = gigneo_profile_stat( 'completed_orders_count', $freelancer_profile_id );
		$completed_orders_employer   = gigneo_profile_stat( 'completed_orders_count', $profile_id );

		update_post_meta( $freelancer_profile_id, '_gigneo_pm_orders_completed', intval( $completed_orders_freelancer['lifetime'] ) );
		update_post_meta( $profile_id, '_gigneo_pm_orders_completed', intval( $completed_orders_employer['lifetime'] ) );

		$order_complete_nf_title   = esc_html__( 'Order completed', 'gigneo' );
		$order_complete_nf_content = sprintf(
		// translators: 1: Job title 2: Profile name
			esc_html__( 'The job order "%1$s" has been completed by %2$s.', 'gigneo' ),
			esc_html( $job_title ),
			esc_html( gigneo_profile_name( $profile_id ) )
		);
		Notifications::trigger_notification( $order_complete_nf_title, $order_complete_nf_content, 'order', 'order_completed', $freelancer_id, $order_id );

		// Trigger Email.
		$wc_mailer          = WC()->mailer();
		$wc_emails          = $wc_mailer->get_emails();
		$freelancer_user_id = get_post_field( 'post_author', $freelancer_id );
		// Email for Freelancer.
		if ( isset( $wc_emails['Gigneo_Job_Completed'] ) ) {
			$wc_emails['Gigneo_Job_Completed']->trigger( $freelancer_user_id, $order_id );
		}

		wp_send_json_success( array( 'message' => esc_html__( 'Order complete successfully.', 'gigneo' ) ), 200 );
	}

	/**
	 * Job order cancel via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function job_order_cancel() {
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$profile_id   = gigneo_active_profile_id();
		$profile_type = gigneo_profile_type( $profile_id );
		$current_user = wp_get_current_user();

		if ( empty( $current_user->ID ) || empty( $profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$order_id     = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;
		$order_status = get_post_status( $order_id );

		if ( empty( $order_id ) || ! in_array( $order_status, array( 'publish', 'delivered' ), true ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$employer_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$freelancer_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		$job_id        = gigneo_post_meta( $order_id, '_gigneo_pm_job_id' );

		if ( absint( $freelancer_id ) !== absint( $profile_id ) && absint( $employer_id ) !== absint( $profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$response = array();

		if ( 'freelancer' === $profile_type ) {
			$order_cancel_activity = gigneo_trigger_order_activity( $order_id, 'order_cancelled', $profile_id );
			if ( is_wp_error( $order_cancel_activity ) ) {
				wp_send_json_error( array( 'message' => $order_cancel_activity->get_error_message() ), 400 );
			}

			$order_cancel_status = wp_update_post(
				array(
					'ID'          => $order_id,
					'post_status' => 'cancelled',
				)
			);

			if ( is_wp_error( $order_cancel_status ) ) {
				wp_send_json_error( array( 'message' => $order_cancel_status->get_error_message() ), 400 );
			}

			$employer_user_id                = get_post_field( 'post_author', $employer_id );
			$order_total                     = gigneo_post_meta( $order_id, '_gigneo_pm_subtotal' );
			$order_platform_commission       = gigneo_post_meta( $order_id, '_gigneo_pm_commission' );
			$order_platform_commission_total = $order_platform_commission['total'] ?? 0;

			$order_invoice_args = array(
				'post_type'   => 'gigneo_invoice',
				'post_title'  => sprintf(
				// translators: %s is an order ID.
					esc_html__( 'Job order refunded — %s', 'gigneo' ),
					esc_html( gigneo_format_id( $order_id, 'order', false ) )
				),
				'post_status' => 'refunded',
				'post_author' => $employer_user_id,
			);
			$order_invoice_id   = wp_insert_post( $order_invoice_args );

			if ( ! is_wp_error( $order_invoice_id ) ) {
				update_post_meta( $order_id, '_gigneo_pm_refund_invoice_id', $order_invoice_id );
				update_post_meta( $order_invoice_id, '_gigneo_pm_type', 'job_order' );
				update_post_meta( $order_invoice_id, '_gigneo_pm_profile_id', $employer_id );
				update_post_meta( $order_invoice_id, '_gigneo_pm_order_id', $order_id );
				update_post_meta( $order_invoice_id, '_gigneo_pm_total', floatval( $order_total ) );
			}

			if ( ! empty( $order_platform_commission_total ) ) {
				$commission_invoice_args = array(
					'post_type'   => 'gigneo_invoice',
					'post_title'  => sprintf(
						// translators: %s is order ID.
						esc_html__( 'Job commission refunded — %s', 'gigneo' ),
						esc_html( gigneo_format_id( $order_id, 'order', false ) )
					),
					'post_status' => 'refunded',
					'post_author' => $employer_user_id,
				);
				$commission_invoice_id   = wp_insert_post( $commission_invoice_args );

				if ( ! is_wp_error( $commission_invoice_id ) ) {
					update_post_meta( $order_id, '_gigneo_pm_commission_refund_invoice_id', $order_invoice_id );
					update_post_meta( $commission_invoice_id, '_gigneo_pm_type', 'job_commission' );
					update_post_meta( $commission_invoice_id, '_gigneo_pm_profile_id', $employer_id );
					update_post_meta( $commission_invoice_id, '_gigneo_pm_order_id', $order_id );
					update_post_meta( $commission_invoice_id, '_gigneo_pm_total', floatval( $order_platform_commission_total ) );
				}
			}

			update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_cancelled' );
			gigneo_trigger_order_activity( $order_id, 'order_payment_refunded', $profile_id );

			wp_update_post(
				array(
					'ID'          => $job_id,
					'post_status' => 'cancelled',
				)
			);
			update_post_meta( $order_id, '_gigneo_pm_cancelled_at', current_time( 'mysql' ) );
			update_post_meta( $order_id, '_gigneo_pm_cancelled_at_gmt', current_time( 'mysql', 1 ) );

			$response['message'] = esc_html__( 'Order cancelled successfully.', 'gigneo' );

			$order_cancel_nf_title   = esc_html__( 'Order cancelled', 'gigneo' );
			$order_cancel_nf_content = sprintf(
			// translators: 1: Job title 2: Profile name
				esc_html__( 'Your job order "%1$s" has been cancelled by %2$s.', 'gigneo' ),
				get_the_title( $job_id ),
				gigneo_profile_name( $profile_id )
			);
			Notifications::trigger_notification( $order_cancel_nf_title, $order_cancel_nf_content, 'order', 'order_cancelled', $employer_id, $order_id );

			// Trigger Email.
			$wc_mailer = WC()->mailer();
			$wc_emails = $wc_mailer->get_emails();
			// Email for Employer.
			if ( isset( $wc_emails['Gigneo_Job_Cancelled'] ) ) {
				$wc_emails['Gigneo_Job_Cancelled']->trigger( $employer_id, $order_id );
			}
		} else {
			$allowed_tags = gigneo_allowed_html_tags();
			$reason       = isset( $_POST['order_cancel_reason'] ) ? sanitize_text_field( wp_unslash( $_POST['order_cancel_reason'] ) ) : '';
			$description  = isset( $_POST['order_cancel_description'] ) ? wp_kses( wp_unslash( $_POST['order_cancel_description'] ), $allowed_tags ) : '';

			if ( empty( $reason ) || empty( $description ) ) {
				wp_send_json_error( array( esc_html__( 'Required field could not be empty.', 'gigneo' ) ), 403 );
			}

			$cancel_request_activity = gigneo_post_meta( $order_id, '_gigneo_pm_cancel_request_activity' );
			if ( ! empty( $cancel_request_activity ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Cancel request already submitted.', 'gigneo' ) ), 400 );
			}

			$cancel_activity_meta['_gigneo_cm_cancel_request_info'] = array(
				'status'      => 'requested',
				'reason'      => $reason,
				'description' => $description,
			);

			$order_cancel_activity = gigneo_trigger_order_activity( $order_id, 'order_cancel_requested', $profile_id, $cancel_activity_meta );
			if ( is_wp_error( $order_cancel_activity ) ) {
				wp_send_json_error( array( 'message' => $order_cancel_activity->get_error_message() ), 400 );
			}

			update_post_meta( $order_id, '_gigneo_pm_cancel_request_activity', $order_cancel_activity );

			$order_cancel_req_nf_title   = esc_html__( 'Order cancel request', 'gigneo' );
			$order_cancel_req_nf_content = sprintf(
			// translators: 1: Job title 2: Profile name
				esc_html__( 'Your job order "%1$s" has been requested to cancelled by %2$s.', 'gigneo' ),
				get_the_title( $job_id ),
				gigneo_profile_name( $profile_id )
			);
			Notifications::trigger_notification( $order_cancel_req_nf_title, $order_cancel_req_nf_content, 'order', 'order_cancel_request', $freelancer_id, $order_id );

			// Trigger Email.
			$wc_mailer = WC()->mailer();
			$wc_emails = $wc_mailer->get_emails();
			// Email for Freelancer.
			if ( isset( $wc_emails['Gigneo_Job_Cancel_Request'] ) ) {
				$wc_emails['Gigneo_Job_Cancel_Request']->trigger( $freelancer_id, $order_id );
			}

			$response['message'] = esc_html__( 'Order cancel request submitted successfully.', 'gigneo' );
		}

		wp_send_json_success( $response, 200 );
	}

	/**
	 * Respond to a job order cancel request via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function job_order_cancel_respond() {
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$profile_id   = gigneo_active_profile_id();
		$profile_type = gigneo_profile_type( $profile_id );
		$current_user = wp_get_current_user();

		if ( empty( $current_user->ID ) || empty( $profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$order_id = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;
		$type     = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : '';

		if ( empty( $order_id ) || 'freelancer' !== $profile_type ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$employer_id             = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$freelancer_id           = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		$cancel_request_activity = gigneo_post_meta( $order_id, '_gigneo_pm_cancel_request_activity' );
		$job_id                  = gigneo_post_meta( $order_id, '_gigneo_pm_job_id' );

		if ( absint( $freelancer_id ) !== absint( $profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		if ( empty( $type ) && empty( $cancel_request_activity ) && 'approve' !== $type && 'decline' !== $type ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
		}

		$response = array();

		if ( 'approve' === $type ) {
			gigneo_trigger_order_activity( $order_id, 'order_cancel_request_approved', $profile_id );

			$cancel_activity_meta           = get_comment_meta( $cancel_request_activity, '_gigneo_cm_cancel_request_info', true );
			$cancel_activity_meta['status'] = 'approved';
			update_comment_meta( $cancel_request_activity, '_gigneo_cm_cancel_request_info', $cancel_activity_meta );
			delete_post_meta( $order_id, '_gigneo_pm_cancel_request_activity' );

			gigneo_trigger_order_activity( $order_id, 'order_cancelled', $profile_id );

			$employer_user_id                = get_post_field( 'post_author', $employer_id );
			$order_total                     = gigneo_post_meta( $order_id, '_gigneo_pm_subtotal' );
			$order_platform_commission       = gigneo_post_meta( $order_id, '_gigneo_pm_commission' );
			$order_platform_commission_total = $order_platform_commission['total'] ?? 0;

			$order_invoice_args = array(
				'post_type'   => 'gigneo_invoice',
				'post_title'  => sprintf(
				// translators: %s is an order ID.
					esc_html__( 'Job order refunded — %s', 'gigneo' ),
					esc_html( gigneo_format_id( $order_id, 'order', false ) )
				),
				'post_status' => 'refunded',
				'post_author' => $employer_user_id,
			);
			$order_invoice_id   = wp_insert_post( $order_invoice_args );

			if ( ! is_wp_error( $order_invoice_id ) ) {
				update_post_meta( $order_id, '_gigneo_pm_refund_invoice_id', $order_invoice_id );
				update_post_meta( $order_invoice_id, '_gigneo_pm_type', 'job_order' );
				update_post_meta( $order_invoice_id, '_gigneo_pm_profile_id', $employer_id );
				update_post_meta( $order_invoice_id, '_gigneo_pm_order_id', $order_id );
				update_post_meta( $order_invoice_id, '_gigneo_pm_total', floatval( $order_total ) );
			}

			if ( ! empty( $order_platform_commission_total ) ) {
				$commission_invoice_args = array(
					'post_type'   => 'gigneo_invoice',
					'post_title'  => sprintf(
					// translators: %s is an order ID.
						esc_html__( 'Job commission refunded — %s', 'gigneo' ),
						esc_html( gigneo_format_id( $order_id, 'order', false ) )
					),
					'post_status' => 'refunded',
					'post_author' => $employer_user_id,
				);
				$commission_invoice_id   = wp_insert_post( $commission_invoice_args );

				if ( ! is_wp_error( $commission_invoice_id ) ) {
					update_post_meta( $order_id, '_gigneo_pm_commission_refund_invoice_id', $order_invoice_id );
					update_post_meta( $commission_invoice_id, '_gigneo_pm_type', 'job_commission' );
					update_post_meta( $commission_invoice_id, '_gigneo_pm_profile_id', $employer_id );
					update_post_meta( $commission_invoice_id, '_gigneo_pm_order_id', $order_id );
					update_post_meta( $commission_invoice_id, '_gigneo_pm_total', floatval( $order_platform_commission_total ) );
				}
			}

			update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_cancelled' );
			gigneo_trigger_order_activity( $order_id, 'order_payment_refunded', $profile_id );
			wp_update_post(
				array(
					'ID'          => $order_id,
					'post_status' => 'cancelled',
				)
			);
			wp_update_post(
				array(
					'ID'          => $job_id,
					'post_status' => 'cancelled',
				)
			);

			$order_cancel_nf_title   = esc_html__( 'Job Order Cancel Request Approved', 'gigneo' );
			$order_cancel_nf_content = sprintf(
			// translators: 1: Job title 2: Profile name
				esc_html__( 'Your job order "%1$s" cancelled request has been approved by %2$s.', 'gigneo' ),
				get_the_title( $job_id ),
				gigneo_profile_name( $profile_id )
			);
			$response['message'] = esc_html__( 'Order cancel request approve successfully.', 'gigneo' );

		} else {
			gigneo_trigger_order_activity( $order_id, 'order_cancel_request_rejected', $profile_id );

			$cancel_activity_meta           = get_comment_meta( $cancel_request_activity, '_gigneo_cm_cancel_request_info', true );
			$cancel_activity_meta['status'] = 'rejected';
			update_comment_meta( $cancel_request_activity, '_gigneo_cm_cancel_request_info', $cancel_activity_meta );
			delete_post_meta( $order_id, '_gigneo_pm_cancel_request_activity' );

			$order_cancel_nf_title   = esc_html__( 'Order cancel request rejected', 'gigneo' );
			$order_cancel_nf_content = sprintf(
			// translators: 1: Job title 2: Profile name
				esc_html__( 'Your job order "%1$s" cancelled request has been rejected by %2$s.', 'gigneo' ),
				get_the_title( $job_id ),
				gigneo_profile_name( $profile_id )
			);
			$response['message'] = esc_html__( 'Order cancel request rejected successfully.', 'gigneo' );
		}

		Notifications::trigger_notification( $order_cancel_nf_title, $order_cancel_nf_content, 'order', 'order_cancel_respond', $employer_id, $order_id );
		// Trigger Email.
		$wc_mailer = WC()->mailer();
		$wc_emails = $wc_mailer->get_emails();
		// Email for Employer.
		if ( isset( $wc_emails['Gigneo_Job_Cancel_Response'] ) ) {
			$status_label = 'approve' === $type ? 'accepted' : 'rejected';
			$wc_emails['Gigneo_Job_Cancel_Response']->trigger( $employer_id, $order_id, $status_label );
		}
		wp_send_json_success( $response, 200 );
	}

	/**
	 * Handle job order review via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function job_order_review() {
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$profile_id   = gigneo_active_profile_id();
		$profile_type = gigneo_profile_type( $profile_id );
		$current_user = wp_get_current_user();
		$allowed_tags = gigneo_allowed_html_tags();

		if ( empty( $current_user->ID ) || empty( $profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$order_id = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;

		if ( empty( $order_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$employer_profile_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$freelancer_profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		$job_id                = gigneo_post_meta( $order_id, '_gigneo_pm_job_id' );

		if ( absint( $profile_id ) !== absint( $freelancer_profile_id ) && absint( $profile_id ) !== absint( $employer_profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		if ( 'employer' === $profile_type ) {
			$communication_level = isset( $_POST['communication_level'] ) ? absint( $_POST['communication_level'] ) : 0;
			$quality_work        = isset( $_POST['quality_work'] ) ? absint( $_POST['quality_work'] ) : 0;
			$understanding       = isset( $_POST['understanding'] ) ? absint( $_POST['understanding'] ) : 0;
			$feedback            = isset( $_POST['review_feedback'] ) ? wp_kses( wp_unslash( $_POST['review_feedback'] ), $allowed_tags ) : '';

			if ( empty( $communication_level ) || empty( $quality_work ) || empty( $understanding ) || empty( $feedback ) ) {
				wp_send_json_error( array( esc_html__( 'Required field could not be empty.', 'gigneo' ) ) );
			}

			$freelancer_review_activity = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_review_activity' );
			if ( ! empty( $freelancer_review_activity ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Already submitted a review.', 'gigneo' ) ), 400 );
			}

			$rating_total   = $communication_level + $quality_work + $understanding;
			$rating_average = round( $rating_total / 3, 2 );

			$freelancer_review_meta['_gigneo_cm_review_info']           = array(
				'communication_level' => $communication_level,
				'quality_work'        => $quality_work,
				'understanding'       => $understanding,
				'feedback'            => $feedback,
			);
			$freelancer_review_meta['_gigneo_cm_freelancer_profile_id'] = $freelancer_profile_id;
			$freelancer_review_meta['_gigneo_cm_average_rating']        = $rating_average;

			$order_freelancer_review = gigneo_trigger_order_activity( $order_id, 'freelancer_review', $profile_id, $freelancer_review_meta );
			if ( is_wp_error( $order_freelancer_review ) ) {
				wp_send_json_error( array( 'message' => $order_freelancer_review->get_error_message() ), 400 );
			}

			update_post_meta( $order_id, '_gigneo_pm_freelancer_review_activity', $order_freelancer_review );
			update_post_meta( $order_id, '_gigneo_pm_average_rating', $rating_average );

			// Freelancer rating - use job_orders_rating.
			$freelancer_average_ratings              = gigneo_post_meta( $freelancer_profile_id, '_gigneo_pm_job_orders_rating' );
			$freelancer_average_ratings              = ! empty( $freelancer_average_ratings ) && is_array( $freelancer_average_ratings ) ? $freelancer_average_ratings : array();
			$freelancer_average_ratings[ $order_id ] = $rating_average;
			update_post_meta( $freelancer_profile_id, '_gigneo_pm_job_orders_rating', $freelancer_average_ratings );

			$freelancer_average_rating = gigneo_profile_rating( $freelancer_profile_id, 'average' );
			update_post_meta( $freelancer_profile_id, '_gigneo_pm_average_rating', $freelancer_average_rating );

			$order_review_nf_title   = esc_html__( 'Order feedback', 'gigneo' );
			$order_review_nf_content = sprintf(
			// translators: 1: Job Title, 2: Profile Name
				esc_html__( 'You have a review on "%1$s" by %2$s.', 'gigneo' ),
				get_the_title( $job_id ),
				gigneo_profile_name( $profile_id )
			);
			Notifications::trigger_notification( $order_review_nf_title, $order_review_nf_content, 'order', 'order_review', $freelancer_profile_id, $order_id );

		} else {
			$communication_level     = isset( $_POST['communication_level'] ) ? absint( $_POST['communication_level'] ) : 0;
			$clarity_of_requirements = isset( $_POST['clarity_of_requirements'] ) ? absint( $_POST['clarity_of_requirements'] ) : 0;
			$professionalism         = isset( $_POST['professionalism'] ) ? absint( $_POST['professionalism'] ) : 0;
			$feedback                = isset( $_POST['review_feedback'] ) ? wp_kses( wp_unslash( $_POST['review_feedback'] ), $allowed_tags ) : '';

			if ( empty( $communication_level ) || empty( $clarity_of_requirements ) || empty( $professionalism ) || empty( $feedback ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Required field could not be empty.', 'gigneo' ) ) );
			}

			$employer_review_activity = gigneo_post_meta( $order_id, '_gigneo_pm_employer_review_activity' );
			if ( ! empty( $employer_review_activity ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Already submitted a review.', 'gigneo' ) ), 400 );
			}

			$rating_total   = $communication_level + $clarity_of_requirements + $professionalism;
			$rating_average = round( $rating_total / 3, 2 );

			$employer_review_meta = array(
				'_gigneo_cm_employer_review_info' => array(
					'communication_level'     => $communication_level,
					'clarity_of_requirements' => $clarity_of_requirements,
					'professionalism'         => $professionalism,
					'feedback'                => $feedback,
				),
				'_gigneo_cm_employer_profile_id'  => $employer_profile_id,
				'_gigneo_cm_average_rating'       => $rating_average,
			);

			$order_employer_review = gigneo_trigger_order_activity( $order_id, 'employer_review', $profile_id, $employer_review_meta );
			if ( is_wp_error( $order_employer_review ) ) {
				wp_send_json_error( array( 'message' => $order_employer_review->get_error_message() ), 400 );
			}

			update_post_meta( $order_id, '_gigneo_pm_employer_review_activity', $order_employer_review );

			$employer_ratings              = gigneo_post_meta( $employer_profile_id, '_gigneo_pm_job_orders_rating' );
			$employer_ratings              = is_array( $employer_ratings ) ? $employer_ratings : array();
			$employer_ratings[ $order_id ] = $rating_average;
			update_post_meta( $employer_profile_id, '_gigneo_pm_job_orders_rating', $employer_ratings );

			$employer_average_rating = gigneo_profile_rating( $employer_profile_id, 'average' );
			update_post_meta( $employer_profile_id, '_gigneo_pm_average_rating', $employer_average_rating );

			$order_review_nf_title   = esc_html__( 'Order feedback', 'gigneo' );
			$order_review_nf_content = sprintf(
			// translators: 1: Job Title, 2: Profile Name
				esc_html__( 'You have a review on "%1$s" by %2$s.', 'gigneo' ),
				get_the_title( $job_id ),
				gigneo_profile_name( $profile_id )
			);
			Notifications::trigger_notification( $order_review_nf_title, $order_review_nf_content, 'order', 'order_review', $employer_profile_id, $order_id );
		}

		// Trigger Email.
		$wc_mailer          = WC()->mailer();
		$wc_emails          = $wc_mailer->get_emails();
		$current_profile_id = gigneo_active_profile_id(); // reviewer.
		$order_id           = absint( $order_id );

		if ( isset( $wc_emails['Gigneo_Job_Review'] ) ) {
			$wc_emails['Gigneo_Job_Review']->trigger( $current_profile_id, $order_id );
		}

		wp_send_json_success( array( 'message' => esc_html__( 'Review submitted successfully.', 'gigneo' ) ) );
	}

	/**
	 * Handles job order attachments upload via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function job_order_attachments_upload() {
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$profile_id   = gigneo_active_profile_id();
		$current_user = wp_get_current_user();

		if ( empty( $current_user->ID ) || empty( $profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$order_id = isset( $_POST['order_id'] ) ? absint( wp_unslash( $_POST['order_id'] ) ) : 0;

		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- File uploads validated per-field below (name, tmp_name, size, extension).
		$files = isset( $_FILES['attachments'] ) ? $_FILES['attachments'] : null;

		if ( empty( $files['name'] ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
		}

		$profile_type          = gigneo_profile_type( $profile_id );
		$job_title             = gigneo_post_meta( $order_id, '_gigneo_pm_job_title' );
		$employer_profile_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$freelancer_profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );

		if ( absint( $profile_id ) !== absint( $employer_profile_id ) && absint( $profile_id ) !== absint( $freelancer_profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$order_status = get_post_status( $order_id );
		if ( ! in_array( $order_status, array( 'publish', 'delivered' ), true ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$job_order_extensions = gigneo_option( '_gigneo_ts_job_order_extensions', array( 'jpg', 'jpeg', 'png', 'doc' ) );
		$job_order_max_files  = gigneo_option( '_gigneo_ts_job_order_max_files', '3' );
		$job_order_max_size   = gigneo_option( '_gigneo_ts_job_order_max_size', '1' );
		$max_file_size        = $job_order_max_size * 1024 * 1024;

		$files_collection = array();

		if ( $files && is_array( $files['name'] ) ) {
			$total_files = count( $files['name'] );

			if ( $total_files > $job_order_max_files ) {
				wp_send_json_error(
					array(
						'message' => sprintf(
						// translators: %s is a max number of files.
							esc_html__( 'You can only upload up to %s files.', 'gigneo' ),
							$job_order_max_files
						),
					),
					403
				);
			}

			for ( $i = 0; $i < $total_files; $i++ ) {

				$error = isset( $files['error'][ $i ] )
					? absint( $files['error'][ $i ] )
					: UPLOAD_ERR_NO_FILE;

				$size = isset( $files['size'][ $i ] )
					? absint( $files['size'][ $i ] )
					: 0;

				$name = isset( $files['name'][ $i ] )
					? sanitize_file_name( wp_unslash( $files['name'][ $i ] ) )
					: '';

				$tmp_name = isset( $files['tmp_name'][ $i ] )
					? sanitize_text_field( $files['tmp_name'][ $i ] )
					: '';

				$ext = strtolower( pathinfo( $name, PATHINFO_EXTENSION ) );

				// Check upload errors.
				if ( UPLOAD_ERR_OK !== $error ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: %s: File name. */
								esc_html__( 'Error uploading file: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				// Validate uploaded file.
				if ( empty( $tmp_name ) || ! is_uploaded_file( $tmp_name ) ) {
					wp_send_json_error(
						array(
							'message' => esc_html__( 'Invalid uploaded file.', 'gigneo' ),
						),
						403
					);
				}

				// Validate file extension.
				if ( ! in_array( $ext, $job_order_extensions, true ) ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: %s: File name. */
								esc_html__( 'File type not allowed: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				$filetype = wp_check_filetype_and_ext( $tmp_name, $name );

				if ( empty( $filetype['ext'] ) || empty( $filetype['type'] ) || $filetype['ext'] !== $ext ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: %s: File name. */
								esc_html__( 'File type not allowed: %s', 'gigneo' ),
								esc_html( $name )
							),
						),
						403
					);
				}

				// Validate file size.
				if ( $size > $max_file_size ) {
					wp_send_json_error(
						array(
							'message' => sprintf(
							/* translators: 1: File name, 2: File max size. */
								esc_html__( '%1$s exceeds the maximum file size of %2$s MB.', 'gigneo' ),
								esc_html( $name ),
								esc_html( $job_order_max_size )
							),
						),
						403
					);
				}

				$files_collection[] = array(
					'name'     => $name,
					'tmp_name' => $tmp_name,
					'size'     => $size,
					'ext'      => sanitize_key( $ext ),
				);
			}

			if ( empty( $files_collection ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
			}

			global $wpdb;
			$table_attachments = $wpdb->prefix . 'gigneo_attachments';
			$job_order_dir     = gigneo_upload_dir_path( '/' . gmdate( 'Y' ) . '/' . gmdate( 'm' ) . '/order/' . $order_id );

			if ( ! file_exists( $job_order_dir ) ) {
				wp_mkdir_p( $job_order_dir );
			}

			foreach ( $files_collection as $file ) {
				$name          = $file['name'];
				$tmp_name      = $file['tmp_name'];
				$size          = $file['size'];
				$file_ext      = pathinfo( $name, PATHINFO_EXTENSION );
				$random_number = wp_rand( 1000, 9999 );
				$file_name     = pathinfo( sanitize_file_name( $name ), PATHINFO_FILENAME ) . '-' . $random_number . '.' . $file_ext;
				$file_name     = wp_unique_filename( $job_order_dir, $file_name );
				$file_path     = $job_order_dir . '/' . $file_name;

				if ( is_uploaded_file( $tmp_name ) && gigneo_move_uploaded_file( $tmp_name, $file_path ) ) {
					$path = str_replace( gigneo_upload_dir_path(), '', $file_path );
					$url  = str_replace( gigneo_upload_dir_path(), gigneo_upload_dir_url(), $file_path );
					$meta = array( 'type' => 'upload' );

					// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
					$wpdb->insert(
						$table_attachments,
						array(
							'post_id'         => $order_id,
							'profile_id'      => absint( $profile_id ),
							'name'            => $name,
							'mime_type'       => $file_ext,
							'size'            => $size,
							'guid'            => $url,
							'path'            => $path,
							'post_type'       => 'order',
							'meta'            => maybe_serialize( $meta ),
							'uploaded_at'     => current_time( 'mysql' ),
							'uploaded_at_gmt' => current_time( 'mysql', 1 ),
						),
						array( '%d', '%d', '%s', '%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s' )
					);
					// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
				}
			}
		}

		gigneo_trigger_order_activity( $order_id, 'attachment_upload' );

		$attachment_uploaded_nf_title   = esc_html__( 'Attachment uploaded', 'gigneo' );
		$attachment_uploaded_nf_content = sprintf(
		// translators: 1: Job title 2: Profile name
			esc_html__( 'A new attachment has been uploaded for the job order "%1$s" by %2$s.', 'gigneo' ),
			esc_html( $job_title ),
			esc_html( gigneo_profile_name( $profile_id ) )
		);
		Notifications::trigger_notification( $attachment_uploaded_nf_title, $attachment_uploaded_nf_content, 'order', 'attachment_uploaded', 'freelancer' === $profile_type ? $employer_profile_id : $freelancer_profile_id, $order_id );

		wp_send_json_success( array( 'message' => esc_html__( 'Attachment uploaded successfully.', 'gigneo' ) ), 200 );
	}

	/**
	 * Handles service order invoice download via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function order_invoice_download() {
		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$profile_id   = gigneo_active_profile_id();
		$current_user = wp_get_current_user();

		if ( empty( $current_user->ID ) || empty( $profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$invoice_id = isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0;

		$invoice_profile_id = gigneo_post_meta( $invoice_id, '_gigneo_pm_profile_id' );

		if ( absint( $profile_id ) !== absint( $invoice_profile_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$invoice_pdf = Invoices::generate_pdf( $invoice_id );

		if ( empty( $invoice_pdf ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
		}

		wp_send_json_success( $invoice_pdf, 200 );
	}

	/**
	 * Handles Moderator service order invoice download via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function moderator_invoice_download() {
		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$current_user = wp_get_current_user();
		$user_id      = $current_user->ID;

		if ( empty( $user_id ) || ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$invoice_id = isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0;

		$invoice_pdf = Invoices::generate_pdf( $invoice_id );

		if ( empty( $invoice_pdf ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
		}

		wp_send_json_success( $invoice_pdf, 200 );
	}

	/**
	 * Handle adding or updating private notes for an order.
	 *
	 * @since 0.0.1
	 */
	public function order_private_note() {

		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$note_id    = isset( $_POST['note_id'] ) ? absint( $_POST['note_id'] ) : 0;
		$order_id   = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;
		$order_note = isset( $_POST['note_content'] ) ? wp_kses( wp_unslash( $_POST['note_content'] ), gigneo_allowed_html_tags() ) : '';

		if ( empty( $order_id ) || empty( $order_note ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 403 );
		}

		if ( ! get_post( $order_id ) ) {
			wp_send_json_error(
				array(
					'message' => esc_html__( 'Something went wrong.', 'gigneo' ),
				),
				403
			);
		}

		$current_user = wp_get_current_user();
		$profile_id   = gigneo_active_profile_id();
		$post_status  = get_post_status( $order_id );

		$employer_profile_id   = (int) gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$freelancer_profile_id = (int) gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );

		if ( ! in_array( $profile_id, array( $employer_profile_id, $freelancer_profile_id ), true ) ) {
			wp_send_json_error(
				array(
					'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ),
				),
				403
			);
		}

		if ( $note_id ) {

			$comment = get_comment( $note_id );

			if ( ! $comment ) {
				wp_send_json_error(
					array(
						'message' => esc_html__( 'Something went wrong.', 'gigneo' ),
					),
					403
				);
			}

			// Ensure note belongs to this order.
			if ( (int) $comment->comment_post_ID !== $order_id ) {
				wp_send_json_error(
					array(
						'message' => esc_html__( 'Something went wrong.', 'gigneo' ),
					),
					403
				);
			}

			// Ensure correct comment type.
			if ( 'order_private_note' !== $comment->comment_type ) {
				wp_send_json_error(
					array(
						'message' => esc_html__( 'Something went wrong.', 'gigneo' ),
					),
					403
				);
			}

			/**
			 * Optional ownership check.
			 * Remove this block if both order participants should be able to edit any note.
			 */
			if ( (int) $comment->user_id !== $current_user->ID ) {
				wp_send_json_error(
					array(
						'message' => esc_html__( 'Something went wrong.', 'gigneo' ),
					),
					403
				);
			}

			$updated = wp_update_comment(
				array(
					'comment_ID'      => $note_id,
					'comment_content' => $order_note,
				),
				true
			);

			if ( is_wp_error( $updated ) ) {
				wp_send_json_error(
					array(
						'message' => esc_html__( 'Something went wrong.', 'gigneo' ),
					),
					500
				);
			}

			$response['comment_id'] = $note_id;
			$response['message']    = esc_html__( 'Note updated successfully.', 'gigneo' );

		} else {

			if ( ! in_array( $post_status, array( 'publish', 'delivered' ), true ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'You can only add private notes to active order.', 'gigneo' ) ), 403 );
			}

			$comment_id = wp_insert_comment(
				array(
					'comment_post_ID'      => $order_id,
					'comment_content'      => $order_note,
					'user_id'              => $current_user->ID ?? 0,
					'comment_author'       => get_userdata( get_current_user_id() )->display_name,
					'comment_author_email' => get_the_author_meta( 'user_email', get_current_user_id() ),
					'comment_type'         => 'order_private_note',
					'comment_approved'     => 1,
				)
			);

			if ( ! $comment_id ) {
				wp_send_json_error(
					array(
						'message' => esc_html__( 'Something went wrong.', 'gigneo' ),
					),
					500
				);
			}

			$response['comment_id'] = $comment_id;
			$response['message']    = esc_html__( 'Note added successfully.', 'gigneo' );
		}

		// Render updated notes section.
		ob_start();
		gigneo_load_template( 'dashboard/order/service/parts/detail/private-notes', true, array( 'order_id' => $order_id ) );
		$notes_markup                        = ob_get_clean();
		$response['markup']['private-notes'] = $notes_markup;

		wp_send_json_success( $response, 200 );
	}

	/**
	 * Handle Deleting Notes.
	 *
	 * @since 0.0.1
	 */
	public function delete_private_note() {

		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$note_id  = absint( $_POST['note_id'] ?? 0 );
		$order_id = absint( $_POST['order_id'] ?? 0 );

		if ( empty( $note_id ) ) {
			wp_send_json_error(
				array( 'message' => esc_html__( 'Some thing went wrong.', 'gigneo' ) ),
				403
			);
		}

		$profile_id            = (int) gigneo_active_profile_id();
		$freelancer_profile_id = (int) gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		$employer_profile_id   = (int) gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );

		if ( $profile_id !== $employer_profile_id && $profile_id !== $freelancer_profile_id ) {
			wp_send_json_error(
				array(
					'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ),
				),
				403
			);
		}

		// Permanently delete the comment (private note).
		$comment = get_comment( $note_id );

		if ( ! $comment ) {
			wp_send_json_error(
				array(
					'message' => esc_html__( 'Something went wrong.', 'gigneo' ),
				),
				403
			);
		}

		// Ensure note belongs to this order.
		if ( (int) $comment->comment_post_ID !== $order_id ) {
			wp_send_json_error(
				array(
					'message' => esc_html__( 'Something went wrong.', 'gigneo' ),
				),
				403
			);
		}

		// Ensure this is a private note.
		if ( 'order_private_note' !== $comment->comment_type ) {
			wp_send_json_error(
				array(
					'message' => esc_html__( 'Something went wrong.', 'gigneo' ),
				),
				403
			);
		}

		// Optional: only allow note author to delete.
		if ( get_current_user_id() !== (int) $comment->user_id ) {
			wp_send_json_error(
				array(
					'message' => esc_html__( 'Something went wrong.', 'gigneo' ),
				),
				403
			);
		}

		$deleted = wp_delete_comment( $note_id, true );

		if ( ! $deleted ) {
			wp_send_json_error(
				array(
					'message' => esc_html__( 'Something went wrong.', 'gigneo' ),
				),
				500
			);
		}

		// Render updated notes section.
		ob_start();
		gigneo_load_template( 'dashboard/order/service/parts/detail/private-notes', true, array( 'order_id' => $order_id ) );
		$notes_markup                        = ob_get_clean();
		$response['markup']['private-notes'] = $notes_markup;

		wp_send_json_success( $response, 200 );
	}

	/**
	 * Complete Service Order by Moderator via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function service_order_complete_by_moderator() {
		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$current_user = wp_get_current_user();
		$user_id      = $current_user->ID;

		if ( empty( $user_id ) || ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$allowed_tags = gigneo_allowed_html_tags();

		$order_id      = isset( $_POST['order_id'] ) ? absint( wp_unslash( $_POST['order_id'] ) ) : 0;
		$reason        = ! empty( $_POST['reason'] ) ? sanitize_text_field( wp_unslash( $_POST['reason'] ) ) : '';
		$description   = ! empty( $_POST['description'] ) ? wp_kses( wp_unslash( $_POST['description'] ), $allowed_tags ) : '';
		$order_status  = get_post_status( $order_id );
		$freelancer_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		$service_id    = gigneo_post_meta( $order_id, '_gigneo_pm_service_id' );

		if ( empty( $order_id ) || 'publish' !== $order_status ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$complete_activity_meta['_gigneo_cm_complete_info'] = array(
			'reason'      => $reason,
			'description' => $description,
		);

		$order_complete_activity = gigneo_trigger_order_activity( $order_id, 'order_completed_by_moderator', $user_id, $complete_activity_meta );
		if ( is_wp_error( $order_complete_activity ) ) {
			wp_send_json_error( array( 'message' => $order_complete_activity->get_error_message() ), 400 );
		}

		// Activity Stage - Order Completed.
		update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_completed' );

		$total_order_completed = gigneo_post_meta( $service_id, '_gigneo_pm_total_completed_order' );
		update_post_meta( $service_id, '_gigneo_pm_total_completed_order', intval( $total_order_completed ) + 1 );

		update_post_meta( $order_id, '_gigneo_pm_cancel_reason', $reason );
		update_post_meta( $order_id, '_gigneo_pm_cancel_description', $description );

		$freelancer_user_id              = get_post_field( 'post_author', $freelancer_id );
		$order_total                     = gigneo_post_meta( $order_id, '_gigneo_pm_subtotal' );
		$order_location                  = gigneo_post_meta( $order_id, '_gigneo_pm_location' );
		$travel_charges                  = isset( $order_location['total_travel_charges'] ) ? floatval( $order_location['total_travel_charges'] ) : 0;
		$commission_total                = $order_total - $travel_charges;
		$order_platform_commission       = gigneo_commission( $commission_total, 'service', $freelancer_id );
		$order_platform_commission_total = $order_platform_commission['total'] ?? 0;

		// Create Invoice for Order.
		$order_invoice_args = array(
			'post_type'   => 'gigneo_invoice',
			'post_title'  => sprintf(
			// translators: %s is Order ID
				esc_html__( 'Service Order Complete — %s', 'gigneo' ),
				esc_html( gigneo_format_id( $order_id, 'order', false ) )
			),
			'post_status' => 'completed',
			'post_author' => $freelancer_user_id,
		);

		$order_invoice_id = wp_insert_post( $order_invoice_args );

		if ( is_wp_error( $order_invoice_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
		}

		update_post_meta( $order_id, '_gigneo_pm_freelancer_invoice_id', $order_invoice_id );

		update_post_meta( $order_invoice_id, '_gigneo_pm_type', 'service_order' );
		update_post_meta( $order_invoice_id, '_gigneo_pm_profile_id', $freelancer_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_order_id', $order_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_total', floatval( $order_total ) );

		// Create Invoice for Commission.
		if ( ! empty( $order_platform_commission_total ) ) {
			$commission_invoice_args = array(
				'post_type'   => 'gigneo_invoice',
				'post_title'  => sprintf(
				// translators: %s is Order ID
					esc_html__( 'Service Order Commission — %s', 'gigneo' ),
					esc_html( gigneo_format_id( $order_id, 'order', false ) )
				),
				'post_status' => 'paid',
				'post_author' => $freelancer_user_id,
			);

			$commission_invoice_id = wp_insert_post( $commission_invoice_args );

			if ( is_wp_error( $commission_invoice_id ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
			}

			update_post_meta( $order_id, '_gigneo_pm_commission_invoice_id', $commission_invoice_id );

			update_post_meta( $commission_invoice_id, '_gigneo_pm_type', 'service_commission' );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_profile_id', $freelancer_id );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_order_id', $order_id );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_total', -floatval( $order_platform_commission_total ) );
		}

		$order_payment_activity = gigneo_trigger_order_activity( $order_id, 'order_payment_released', $user_id );
		if ( is_wp_error( $order_payment_activity ) ) {
			wp_send_json_error( array( 'message' => $order_payment_activity->get_error_message() ), 400 );
		}

		wp_update_post(
			array(
				'ID'          => $order_id,
				'post_status' => 'completed',
			)
		);

		update_post_meta( $order_id, '_gigneo_pm_completed_at', current_time( 'mysql' ) );
		update_post_meta( $order_id, '_gigneo_pm_completed_at_gmt', current_time( 'mysql', 1 ) );

		$freelancer_profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		$employer_profile_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );

		$completed_orders_freelancer = gigneo_profile_stat( 'completed_orders_count', $freelancer_profile_id );
		$completed_orders_employer   = gigneo_profile_stat( 'completed_orders_count', $employer_profile_id );

		update_post_meta( $freelancer_profile_id, '_gigneo_pm_orders_completed', intval( $completed_orders_freelancer['lifetime'] ) );
		update_post_meta( $employer_profile_id, '_gigneo_pm_orders_completed', intval( $completed_orders_employer['lifetime'] ) );

		// Trigger Email.
		$wc_mailer             = WC()->mailer();
		$wc_emails             = $wc_mailer->get_emails();
		$employer_profile_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$freelancer_profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		$service_id            = gigneo_post_meta( $order_id, '_gigneo_pm_service_id' );
		$moderator_name        = gigneo_moderator_name();

		// Email for Freelancer.
		if ( isset( $wc_emails['Gigneo_Service_Moderator_Completed'] ) ) {
			$wc_emails['Gigneo_Service_Moderator_Completed']->trigger( $freelancer_profile_id, $order_id );
		}

		// Email for Employer.
		if ( isset( $wc_emails['Gigneo_Service_Moderator_Completed'] ) ) {
			$wc_emails['Gigneo_Service_Moderator_Completed']->trigger( $employer_profile_id, $order_id );
		}
		$order_completed_nf_title   = esc_html__( 'Order completed', 'gigneo' );
		$order_completed_nf_content = sprintf(
		// translators: 1: Service Title, 2: Profile Name
			esc_html__( 'You service order "%1$s" completed by %2$s.', 'gigneo' ),
			get_the_title( $service_id ),
			$moderator_name
		);

		Notifications::trigger_notification( $order_completed_nf_title, $order_completed_nf_content, 'order', 'order_completed_by_moderator', $employer_profile_id, $order_id );
		Notifications::trigger_notification( $order_completed_nf_title, $order_completed_nf_content, 'order', 'order_completed_by_moderator', $freelancer_profile_id, $order_id );

		wp_send_json_success( array( 'message' => esc_html__( 'Order complete successfully.', 'gigneo' ) ), 200 );
	}

	/**
	 * Admin Service order cancel via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function service_order_cancel_by_moderator() {
		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$current_user = wp_get_current_user();
		$user_id      = $current_user->ID;

		if ( empty( $user_id ) || ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$allowed_tags = gigneo_allowed_html_tags();

		$order_id     = isset( $_POST['order_id'] ) ? absint( wp_unslash( $_POST['order_id'] ) ) : 0;
		$reason       = ! empty( $_POST['reason'] ) ? sanitize_text_field( wp_unslash( $_POST['reason'] ) ) : '';
		$description  = ! empty( $_POST['description'] ) ? wp_kses( wp_unslash( $_POST['description'] ), $allowed_tags ) : '';
		$order_status = get_post_status( $order_id );
		$employer_id  = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );

		if ( empty( $order_id ) || 'publish' !== $order_status ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$response = array();

		$cancel_activity_meta['_gigneo_cm_cancel_info'] = array(
			'reason'      => $reason,
			'description' => $description,
		);

		$order_cancel_activity = gigneo_trigger_order_activity( $order_id, 'order_cancelled_by_moderator', $user_id, $cancel_activity_meta );

		if ( is_wp_error( $order_cancel_activity ) ) {
			wp_send_json_error( array( 'message' => $order_cancel_activity->get_error_message() ), 400 );
		}

		$employer_user_id                = get_post_field( 'post_author', $employer_id );
		$order_total                     = gigneo_post_meta( $order_id, '_gigneo_pm_subtotal' );
		$order_platform_commission       = gigneo_post_meta( $order_id, '_gigneo_pm_commission' );
		$order_platform_commission_total = $order_platform_commission['total'] ?? 0;

		// Create Invoice for Order.
		$order_invoice_args = array(
			'post_type'   => 'gigneo_invoice',
			'post_title'  => sprintf(
			// translators: %s is Order ID
				esc_html__( 'Service order refunded — %s', 'gigneo' ),
				esc_html( gigneo_format_id( $order_id, 'order', false ) )
			),
			'post_status' => 'refunded',
			'post_author' => $employer_user_id,
		);

		$order_invoice_id = wp_insert_post( $order_invoice_args );

		if ( is_wp_error( $order_invoice_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
		}

		update_post_meta( $order_id, '_gigneo_pm_refund_invoice_id', $order_invoice_id );

		update_post_meta( $order_invoice_id, '_gigneo_pm_type', 'service_order' );
		update_post_meta( $order_invoice_id, '_gigneo_pm_profile_id', $employer_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_order_id', $order_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_total', floatval( $order_total ) );

		// Create Invoice for Commission.
		if ( ! empty( $order_platform_commission_total ) ) {
			$commission_invoice_args = array(
				'post_type'   => 'gigneo_invoice',
				'post_title'  => sprintf(
				// translators: %s is Order ID
					esc_html__( 'Service commission refunded — %s', 'gigneo' ),
					esc_html( gigneo_format_id( $order_id, 'order', false ) )
				),
				'post_status' => 'refunded',
				'post_author' => $employer_user_id,
			);

			$commission_invoice_id = wp_insert_post( $commission_invoice_args );

			if ( is_wp_error( $commission_invoice_id ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
			}

			update_post_meta( $order_id, '_gigneo_pm_commission_refund_invoice_id', $order_invoice_id );

			update_post_meta( $commission_invoice_id, '_gigneo_pm_type', 'service_commission' );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_profile_id', $employer_id );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_order_id', $order_id );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_total', floatval( $order_platform_commission_total ) );
		}

		// Activity Stage - Order Cancelled.
		update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_cancelled' );

		// Payment Refunded Activity.
		$order_payment_activity = gigneo_trigger_order_activity( $order_id, 'order_payment_refunded', $user_id );
		if ( is_wp_error( $order_payment_activity ) ) {
			wp_send_json_error( array( 'message' => $order_payment_activity->get_error_message() ), 400 );
		}

		wp_update_post(
			array(
				'ID'          => $order_id,
				'post_status' => 'cancelled',
			)
		);

		update_post_meta( $order_id, '_gigneo_pm_cancelled_at', current_time( 'mysql' ) );
		update_post_meta( $order_id, '_gigneo_pm_cancelled_at_gmt', current_time( 'mysql', 1 ) );

		// Trigger Email.
		$wc_mailer             = WC()->mailer();
		$wc_emails             = $wc_mailer->get_emails();
		$employer_profile_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$freelancer_profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		$service_id            = gigneo_post_meta( $order_id, '_gigneo_pm_service_id' );
		$moderator_name        = gigneo_moderator_name();

		// Email for Freelancer.
		if ( isset( $wc_emails['Gigneo_Service_Moderator_Cancelled'] ) ) {
			$wc_emails['Gigneo_Service_Moderator_Cancelled']->trigger( $freelancer_profile_id, $order_id );
		}
		// Email for Employer.
		if ( isset( $wc_emails['Gigneo_Service_Moderator_Cancelled'] ) ) {
			$wc_emails['Gigneo_Service_Moderator_Cancelled']->trigger( $employer_profile_id, $order_id );
		}
		$order_cancelled_nf_title   = esc_html__( 'Order cancelled', 'gigneo' );
		$order_cancelled_nf_content = sprintf(
		// translators: 1: Service Title, 2: Profile Name
			esc_html__( 'You service order "%1$s" cancelled by %2$s.', 'gigneo' ),
			get_the_title( $service_id ),
			esc_html( $moderator_name )
		);

		Notifications::trigger_notification( $order_cancelled_nf_title, $order_cancelled_nf_content, 'order', 'order_cancel_by_moderator', $employer_profile_id, $order_id );
		Notifications::trigger_notification( $order_cancelled_nf_title, $order_cancelled_nf_content, 'order', 'order_cancel_by_moderator', $freelancer_profile_id, $order_id );

		$response['message'] = esc_html__( 'Order cancelled successfully.', 'gigneo' );

		wp_send_json_success( $response, 200 );
	}

	/**
	 * Shortlist Proposal via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function shortlist_proposal() {
		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$proposal_id  = isset( $_POST['id'] ) ? absint( wp_unslash( $_POST['id'] ) ) : 0;
		$job_id       = gigneo_post_meta( $proposal_id, '_gigneo_pm_job_id' );
		$current_user = wp_get_current_user();

		if ( empty( $proposal_id ) || empty( $job_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Invalid request.', 'gigneo' ) ) );
		}

		// Check if current user is the employer of this job.
		$job_author = (int) get_post_field( 'post_author', $job_id );
		if ( $current_user->ID !== $job_author ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$status         = get_post_status( $proposal_id );
		$is_shortlisted = ( 'shortlisted' === $status );
		$new_status     = $is_shortlisted ? 'publish' : 'shortlisted';

		wp_update_post(
			array(
				'ID'          => $proposal_id,
				'post_status' => $new_status,
			)
		);

		if ( 'shortlisted' === $new_status ) {
			$freelancer_profile_id = gigneo_post_meta( $proposal_id, '_gigneo_pm_profile_id' );
			$shortlist_nf_title    = esc_html__( 'Proposal shortlisted', 'gigneo' );
			$shortlist_nf_content  = sprintf(
			// translators: %s is job title
				esc_html__( 'Your proposal for the job "%s" has been shortlisted.', 'gigneo' ),
				get_the_title( $job_id )
			);

			Notifications::trigger_notification( $shortlist_nf_title, $shortlist_nf_content, 'job', 'proposal_shortlisted', $freelancer_profile_id, $proposal_id );
		}

		$response = array(
			'is_shortlisted' => ( 'shortlisted' === $new_status ) ? 'yes' : 'no',
			'label'          => ( 'shortlisted' === $new_status ) ? esc_html__( 'Shortlisted', 'gigneo' ) : esc_html__( 'Shortlist', 'gigneo' ),
		);

		wp_send_json_success( $response );
	}

	/**
	 * Withdraw Proposal via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function withdraw_proposal() {
		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$proposal_id  = isset( $_POST['id'] ) ? absint( wp_unslash( $_POST['id'] ) ) : 0;
		$current_user = wp_get_current_user();
		$profile_id   = gigneo_active_profile_id();

		if ( empty( $proposal_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Invalid proposal ID.', 'gigneo' ) ) );
		}

		$proposal = get_post( $proposal_id );

		if ( ! $proposal || 'gigneo_proposal' !== $proposal->post_type ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Proposal not found.', 'gigneo' ) ) );
		}

		// Check if current user is the author of this proposal.
		if ( (int) $proposal->post_author !== $current_user->ID ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$proposal_status = $proposal->post_status;
		$job_id          = gigneo_post_meta( $proposal_id, '_gigneo_pm_job_id' );
		$job_status      = get_post_status( $job_id );

		// Check if proposal has been viewed by the employer.
		if ( 'publish' !== $proposal_status ) {
			wp_send_json_error( array( 'message' => esc_html__( 'This proposal cannot be withdrawn because it has already been viewed, hired, or rejected by the employer.', 'gigneo' ) ) );
		}

		// Check if the job is still available.
		if ( ! $job_id || ! get_post( $job_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'The associated job no longer exists.', 'gigneo' ) ) );
		}

		$employer_profile_id = gigneo_post_meta( $job_id, '_gigneo_pm_profile_id' );
		$withdraw_nf_title   = esc_html__( 'Proposal withdrawn', 'gigneo' );
		$withdraw_nf_content = sprintf(
		// translators: 1: Freelancer Name, 2: Job Title
			esc_html__( '%1$s has withdrawn their proposal for the job "%2$s".', 'gigneo' ),
			gigneo_profile_name( $profile_id ),
			get_the_title( $job_id )
		);

		Notifications::trigger_notification( $withdraw_nf_title, $withdraw_nf_content, 'job', 'proposal_withdrawn', $employer_profile_id, $job_id );

		// Delete the proposal permanently.
		$deleted = wp_delete_post( $proposal_id, true );

		if ( $deleted ) {
			wp_send_json_success( array( 'message' => esc_html__( 'Your proposal has been withdrawn successfully.', 'gigneo' ) ) );
		} else {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong while withdrawing your proposal.', 'gigneo' ) ) );
		}
	}

	/**
	 * Admin Job order cancel via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function job_order_cancel_by_moderator() {
		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$current_user = wp_get_current_user();
		$user_id      = $current_user->ID;

		if ( empty( $user_id ) || ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$allowed_tags = gigneo_allowed_html_tags();

		$order_id     = isset( $_POST['order_id'] ) ? absint( wp_unslash( $_POST['order_id'] ) ) : 0;
		$reason       = ! empty( $_POST['reason'] ) ? sanitize_text_field( wp_unslash( $_POST['reason'] ) ) : '';
		$description  = ! empty( $_POST['description'] ) ? wp_kses( wp_unslash( $_POST['description'] ), $allowed_tags ) : '';
		$order_status = get_post_status( $order_id );
		$employer_id  = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );

		if ( empty( $order_id ) || 'publish' !== $order_status ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$response = array();

		$cancel_activity_meta['_gigneo_cm_cancel_info'] = array(
			'reason'      => $reason,
			'description' => $description,
		);

		$order_cancel_activity = gigneo_trigger_order_activity( $order_id, 'order_cancelled_by_moderator', $user_id, $cancel_activity_meta );

		if ( is_wp_error( $order_cancel_activity ) ) {
			wp_send_json_error( array( 'message' => $order_cancel_activity->get_error_message() ), 400 );
		}

		$employer_user_id                = get_post_field( 'post_author', $employer_id );
		$order_total                     = gigneo_post_meta( $order_id, '_gigneo_pm_subtotal' );
		$job_id                          = gigneo_post_meta( $order_id, '_gigneo_pm_job_id' );
		$order_platform_commission       = gigneo_post_meta( $order_id, '_gigneo_pm_commission' );
		$order_platform_commission_total = $order_platform_commission['total'] ?? 0;

		// Create Invoice for Order.
		$order_invoice_args = array(
			'post_type'   => 'gigneo_invoice',
			'post_title'  => sprintf(
			// translators: %s is Order ID
				esc_html__( 'Job order refunded — %s', 'gigneo' ),
				esc_html( gigneo_format_id( $order_id, 'order', false ) )
			),
			'post_status' => 'refunded',
			'post_author' => $employer_user_id,
		);

		$order_invoice_id = wp_insert_post( $order_invoice_args );

		if ( is_wp_error( $order_invoice_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
		}

		update_post_meta( $order_id, '_gigneo_pm_refund_invoice_id', $order_invoice_id );

		update_post_meta( $order_invoice_id, '_gigneo_pm_type', 'job_order' );
		update_post_meta( $order_invoice_id, '_gigneo_pm_profile_id', $employer_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_order_id', $order_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_total', floatval( $order_total ) );

		// Create Invoice for Commission.
		if ( ! empty( $order_platform_commission_total ) ) {
			$commission_invoice_args = array(
				'post_type'   => 'gigneo_invoice',
				'post_title'  => sprintf(
				// translators: %s is Order ID
					esc_html__( 'Job commission refunded — %s', 'gigneo' ),
					esc_html( gigneo_format_id( $order_id, 'order', false ) )
				),
				'post_status' => 'refunded',
				'post_author' => $employer_user_id,
			);

			$commission_invoice_id = wp_insert_post( $commission_invoice_args );

			if ( is_wp_error( $commission_invoice_id ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
			}

			update_post_meta( $order_id, '_gigneo_pm_commission_refund_invoice_id', $order_invoice_id );

			update_post_meta( $commission_invoice_id, '_gigneo_pm_type', 'job_commission' );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_profile_id', $employer_id );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_order_id', $order_id );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_total', floatval( $order_platform_commission_total ) );
		}

		// Activity Stage - Order Cancelled.
		update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_cancelled' );

		// Payment Refunded Activity.
		$order_payment_activity = gigneo_trigger_order_activity( $order_id, 'order_payment_refunded', $user_id );
		if ( is_wp_error( $order_payment_activity ) ) {
			wp_send_json_error( array( 'message' => $order_payment_activity->get_error_message() ), 400 );
		}

		wp_update_post(
			array(
				'ID'          => $order_id,
				'post_status' => 'cancelled',
			)
		);
		wp_update_post(
			array(
				'ID'          => $job_id,
				'post_status' => 'cancelled',
			)
		);
		update_post_meta( $order_id, '_gigneo_pm_cancelled_at', current_time( 'mysql' ) );
		update_post_meta( $order_id, '_gigneo_pm_cancelled_at_gmt', current_time( 'mysql', 1 ) );

		// Trigger Email.
		$wc_mailer             = WC()->mailer();
		$wc_emails             = $wc_mailer->get_emails();
		$employer_profile_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );
		$freelancer_profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		$job_id                = gigneo_post_meta( $order_id, '_gigneo_pm_job_id' );
		$moderator_name        = gigneo_moderator_name();
		// Email for Freelancer.
		if ( isset( $wc_emails['Gigneo_Job_Moderator_Cancelled'] ) ) {
			$wc_emails['Gigneo_Job_Moderator_Cancelled']->trigger( $freelancer_profile_id, $order_id );
		}
		// Email for Employer.
		if ( isset( $wc_emails['Gigneo_Job_Moderator_Cancelled'] ) ) {
			$wc_emails['Gigneo_Job_Moderator_Cancelled']->trigger( $employer_profile_id, $order_id );
		}
		$order_cancelled_nf_title   = esc_html__( 'Order cancelled', 'gigneo' );
		$order_cancelled_nf_content = sprintf(
		// translators: 1: Job Title, 2: Profile Name
			esc_html__( 'You job order "%1$s" cancelled by %2$s.', 'gigneo' ),
			get_the_title( $job_id ),
			esc_html( $moderator_name )
		);

		Notifications::trigger_notification( $order_cancelled_nf_title, $order_cancelled_nf_content, 'order', 'order_cancel_by_moderator', $employer_profile_id, $order_id );
		Notifications::trigger_notification( $order_cancelled_nf_title, $order_cancelled_nf_content, 'order', 'order_cancel_by_moderator', $freelancer_profile_id, $order_id );

		$response['message'] = esc_html__( 'Order cancelled successfully.', 'gigneo' );

		wp_send_json_success( $response, 200 );
	}

	/**
	 * Complete Job Order by Moderator via AJAX.
	 *
	 * @since 0.0.1
	 */
	public function job_order_complete_by_moderator() {
		// Check if nonce is valid.
		$security = isset( $_POST['security'] ) ? sanitize_text_field( wp_unslash( $_POST['security'] ) ) : '';
		if ( empty( $security ) || ! wp_verify_nonce( $security, 'gigneo_security' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Security verification failed. Please reload and try again.', 'gigneo' ) ), 403 );
		}

		$current_user = wp_get_current_user();
		$user_id      = $current_user->ID;

		if ( empty( $user_id ) || ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$allowed_tags = gigneo_allowed_html_tags();

		$order_id      = isset( $_POST['order_id'] ) ? absint( wp_unslash( $_POST['order_id'] ) ) : 0;
		$reason        = ! empty( $_POST['reason'] ) ? sanitize_text_field( wp_unslash( $_POST['reason'] ) ) : '';
		$description   = ! empty( $_POST['description'] ) ? wp_kses( wp_unslash( $_POST['description'] ), $allowed_tags ) : '';
		$order_status  = get_post_status( $order_id );
		$job_id        = gigneo_post_meta( $order_id, '_gigneo_pm_job_id' );
		$freelancer_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );

		if ( empty( $order_id ) || 'publish' !== $order_status ) {
			wp_send_json_error( array( 'message' => esc_html__( 'You do not have permission to do that.', 'gigneo' ) ), 403 );
		}

		$complete_activity_meta['_gigneo_cm_complete_info'] = array(
			'reason'      => $reason,
			'description' => $description,
		);

		$order_complete_activity = gigneo_trigger_order_activity( $order_id, 'order_completed_by_moderator', $user_id, $complete_activity_meta );
		if ( is_wp_error( $order_complete_activity ) ) {
			wp_send_json_error( array( 'message' => $order_complete_activity->get_error_message() ), 400 );
		}

		// Activity Stage - Order Completed.
		update_post_meta( $order_id, '_gigneo_pm_activity_stage', 'order_completed' );
		update_post_meta( $order_id, '_gigneo_pm_cancel_reason', $reason );
		update_post_meta( $order_id, '_gigneo_pm_cancel_description', $description );

		$freelancer_user_id              = get_post_field( 'post_author', $freelancer_id );
		$order_total                     = gigneo_post_meta( $order_id, '_gigneo_pm_subtotal' );
		$order_platform_commission       = gigneo_commission( $order_total, 'job', $freelancer_id );
		$order_platform_commission_total = $order_platform_commission['total'] ?? 0;

		// Create Invoice for Order.
		$order_invoice_args = array(
			'post_type'   => 'gigneo_invoice',
			'post_title'  => sprintf(
			// translators: %s is Order ID
				esc_html__( 'Job order complete — %s', 'gigneo' ),
				esc_html( gigneo_format_id( $order_id, 'order', false ) )
			),
			'post_status' => 'completed',
			'post_author' => $freelancer_user_id,
		);

		$order_invoice_id = wp_insert_post( $order_invoice_args );

		if ( is_wp_error( $order_invoice_id ) ) {
			wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
		}

		update_post_meta( $order_id, '_gigneo_pm_freelancer_invoice_id', $order_invoice_id );

		update_post_meta( $order_invoice_id, '_gigneo_pm_type', 'job_order' );
		update_post_meta( $order_invoice_id, '_gigneo_pm_profile_id', $freelancer_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_order_id', $order_id );
		update_post_meta( $order_invoice_id, '_gigneo_pm_total', floatval( $order_total ) );

		// Create Invoice for Commission.
		if ( ! empty( $order_platform_commission_total ) ) {
			$commission_invoice_args = array(
				'post_type'   => 'gigneo_invoice',
				'post_title'  => sprintf(
				// translators: %s is Order ID
					esc_html__( 'Job order commission — %s', 'gigneo' ),
					esc_html( gigneo_format_id( $order_id, 'order', false ) )
				),
				'post_status' => 'paid',
				'post_author' => $freelancer_user_id,
			);

			$commission_invoice_id = wp_insert_post( $commission_invoice_args );

			if ( is_wp_error( $commission_invoice_id ) ) {
				wp_send_json_error( array( 'message' => esc_html__( 'Something went wrong.', 'gigneo' ) ), 400 );
			}

			update_post_meta( $order_id, '_gigneo_pm_commission_invoice_id', $commission_invoice_id );

			update_post_meta( $commission_invoice_id, '_gigneo_pm_type', 'job_commission' );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_profile_id', $freelancer_id );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_order_id', $order_id );
			update_post_meta( $commission_invoice_id, '_gigneo_pm_total', -floatval( $order_platform_commission_total ) );
		}

		$order_payment_activity = gigneo_trigger_order_activity( $order_id, 'order_payment_released', $user_id );
		if ( is_wp_error( $order_payment_activity ) ) {
			wp_send_json_error( array( 'message' => $order_payment_activity->get_error_message() ), 400 );
		}

		wp_update_post(
			array(
				'ID'          => $order_id,
				'post_status' => 'completed',
			)
		);
		wp_update_post(
			array(
				'ID'          => $job_id,
				'post_status' => 'completed',
			)
		);

		update_post_meta( $order_id, '_gigneo_pm_completed_at', current_time( 'mysql' ) );
		update_post_meta( $order_id, '_gigneo_pm_completed_at_gmt', current_time( 'mysql', 1 ) );

		$freelancer_profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		$employer_profile_id   = gigneo_post_meta( $order_id, '_gigneo_pm_profile_id' );

		$completed_orders_freelancer = gigneo_profile_stat( 'completed_orders_count', $freelancer_profile_id );
		$completed_orders_employer   = gigneo_profile_stat( 'completed_orders_count', $employer_profile_id );

		update_post_meta( $freelancer_profile_id, '_gigneo_pm_orders_completed', intval( $completed_orders_freelancer['lifetime'] ) );
		update_post_meta( $employer_profile_id, '_gigneo_pm_orders_completed', intval( $completed_orders_employer['lifetime'] ) );

		// Trigger Email.
		$wc_mailer             = WC()->mailer();
		$wc_emails             = $wc_mailer->get_emails();
		$freelancer_profile_id = gigneo_post_meta( $order_id, '_gigneo_pm_freelancer_profile_id' );
		$moderator_name        = gigneo_moderator_name();

		// Email Notification.
		if ( isset( $wc_emails['Gigneo_Job_Moderator_Completed'] ) ) {
			$wc_emails['Gigneo_Job_Moderator_Completed']->trigger( $freelancer_profile_id, $order_id );
			$wc_emails['Gigneo_Job_Moderator_Completed']->trigger( $employer_profile_id, $order_id );
		}
		$order_completed_nf_title   = esc_html__( 'Order completed', 'gigneo' );
		$order_completed_nf_content = sprintf(
		// translators: 1: Job Title, 2: Profile Name
			esc_html__( 'Your job order "%1$s" completed by %2$s.', 'gigneo' ),
			get_the_title( $job_id ),
			$moderator_name
		);

		Notifications::trigger_notification( $order_completed_nf_title, $order_completed_nf_content, 'order', 'order_completed_by_moderator', $employer_profile_id, $order_id );
		Notifications::trigger_notification( $order_completed_nf_title, $order_completed_nf_content, 'order', 'order_completed_by_moderator', $freelancer_profile_id, $order_id );

		wp_send_json_success( array( 'message' => esc_html__( 'Order complete successfully.', 'gigneo' ) ), 200 );
	}
}
