<?php

namespace SugarCalendarBookings\Services\Admin;

use SugarCalendarBookings\DeleteManager;
use SugarCalendarBookings\Locations\LocationModel;
use SugarCalendarBookings\Locations\OnlineLocationProviderFactory;
use SugarCalendarBookings\Payments\PaymentsHelpers;
use SugarCalendarBookings\Plugin;
use SugarCalendarBookings\Services\ServiceModel;
use SugarCalendarBookings\Support\Admin\Notices;
use SugarCalendarBookings\Support\Database\QueryBuilder;
use SugarCalendarBookings\Support\Helpers\Time;
use SugarCalendarBookings\Support\Helpers\UI;
use SugarCalendarBookings\Support\Tags\TagModel;
use SugarCalendarBookings\Admin\AbstractListTable;

/**
 * Services list table.
 *
 * @since 1.0.0
 * @since 1.2.1 Switched to extend abstract list table.
 */
class ServicesListTable extends AbstractListTable {

	/**
	 * Domain-specific default filters.
	 *
	 * @since 1.0.0
	 * @since 1.2.1 Only domain-specific filters (s, status, order, orderby moved to parent).
	 *
	 * @var array
	 */
	protected $active_filters = [
		'tags'        => null,
		'location_id' => null,
	];

	/**
	 * Domain-specific filter rules.
	 *
	 * @since 1.2.1
	 *
	 * @var array
	 */
	protected $filter_rules = [
		'tags.*'      => 'id',
		'location_id' => 'id',
	];

	/**
	 * Number of services by different statuses.
	 *
	 * @since 1.0.0
	 *
	 * @var array
	 */
	private $counts = [];

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 */
	public function __construct() {

		// Set parent defaults.
		parent::__construct(
			[
				'singular' => 'service',
				'plural'   => 'services',
				'screen'   => 'bookings_page_sugar-calendar-bookings-services',
			]
		);

		// Process filters.
		$this->active_filters = $this->setup_active_filters();
	}

	/**
	 * Define the table columns.
	 *
	 * @since 1.0.0
	 *
	 * @return array Associative array of slug=>Name columns data.
	 */
	public function get_columns() {

		$columns = [];

		$columns['cb']       = '<input type="checkbox" />';
		$columns['service']  = esc_html__( 'Service Name', 'sugar-calendar-bookings-scheduling-appointments-lite' );
		$columns['tags']     = esc_html__( 'Tags', 'sugar-calendar-bookings-scheduling-appointments-lite' );
		$columns['duration'] = esc_html__( 'Duration', 'sugar-calendar-bookings-scheduling-appointments-lite' );
		$columns['price']    = esc_html__( 'Price', 'sugar-calendar-bookings-scheduling-appointments-lite' );
		$columns['location'] = esc_html__( 'Location', 'sugar-calendar-bookings-scheduling-appointments-lite' );
		$columns['status']   = esc_html__( 'Status', 'sugar-calendar-bookings-scheduling-appointments-lite' );

		return $columns;
	}

	/**
	 * Retrieve the view statuses.
	 *
	 * @since 1.0.0
	 */
	public function get_views() {

		$base_url       = $this->get_base_url();
		$current_status = $this->get_selected_status();

		$views = [];

		$views['all'] = sprintf(
			'<a href="%1$s" %2$s>%3$s&nbsp;<span class="count">(%4$d)</span></a>',
			esc_url( remove_query_arg( 'status', $base_url ) ),
			$current_status === null ? 'class="current"' : '',
			esc_html__( 'All', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
			intval( $this->counts['total'] )
		);

		foreach ( $this->get_statuses() as $status => $status_label ) {
			$count = intval( $this->counts[ 'status_' . $status ] );

			// Skipping status with no services.
			if ( $count === 0 && $current_status !== $status ) {
				continue;
			}

			$views[ $status ] = sprintf(
				'<a href="%1$s" %2$s>%3$s&nbsp;<span class="count">(%4$d)</span></a>',
				esc_url( add_query_arg( 'status', $status, $base_url ) ),
				$current_status === $status ? 'class="current"' : '',
				esc_html( $status_label ),
				$count
			);
		}

		return $views;
	}

	/**
	 * Get the base url for the views links.
	 *
	 * @since 1.0.0
	 * @since 1.2.0 Switched to protected method.
	 *
	 * @return string
	 */
	protected function get_base_url() {

		$base_url = ServicesListPage::get_url();
		$filters  = $this->get_applied_filters();

		if ( isset( $filters['status'] ) ) {
			$base_url = add_query_arg( 'status', $filters['status'], $base_url );
		}

		if ( isset( $filters['tags'] ) ) {
			$base_url = add_query_arg( 'tags', implode( ',', $filters['tags'] ), $base_url );
		}

		if ( isset( $filters['location_id'] ) ) {
			$base_url = add_query_arg( 'location_id', $filters['location_id'], $base_url );
		}

		if ( isset( $filters['search'] ) ) {
			$base_url = add_query_arg( 's', $filters['search'], $base_url );
		}

		return $base_url;
	}

	/**
	 * Apply filters to the query.
	 *
	 * @since 1.0.0
	 * @since 1.2.0 Switched to protected method.
	 *
	 * @param QueryBuilder $query   Query builder.
	 * @param array        $filters Query parameters.
	 */
	protected function build_filters_query( $query, $filters ) {

		if ( ! empty( $filters['status'] ) ) {
			$query->where( 'services.status', '=', $filters['status'], '%s' );
		}

		if ( ! empty( $filters['tags'] ) ) {
			$query->join( 'tag_relationships', 'services.id', '=', 'tag_relationships.object_id' )
				->where( 'tag_relationships.object_type', '=', 'service', '%s' )
				->where( 'tag_relationships.tag_id', 'IN', $filters['tags'], '%d' );
		}

		if ( ! empty( $filters['location_id'] ) ) {
			$query->join( 'location_service', 'services.id', '=', 'location_service.service_id' )
				->where( 'location_service.location_id', '=', $filters['location_id'], '%d' );
		}

		if ( ! empty( $filters['search'] ) ) {
			$query->where( 'services.name', 'like', '%' . $filters['search'] . '%', '%s' );
		}
	}

	/**
	 * Get current filters query parameters.
	 *
	 * @since 1.0.0
	 *
	 * @return array
	 */
	public function get_applied_filters() {

		$filters = [
			'status'      => $this->get_selected_status(),
			'tags'        => $this->get_selected_tags(),
			'location_id' => $this->get_selected_location(),
			'search'      => $this->get_search_term(),
		];

		return array_filter( $filters, fn( $v ) => $v !== null );
	}

	/**
	 * Return status filter value or null.
	 *
	 * @since 1.0.0
	 *
	 * @return string|null
	 */
	public function get_selected_status() {

		return $this->get_active_filter( 'status' );
	}

	/**
	 * Return selected tags filter values or null.
	 *
	 * @since 1.0.0
	 *
	 * @return array|null
	 */
	public function get_selected_tags() {

		return $this->get_active_filter( 'tags' );
	}

	/**
	 * Return selected location filter value or null.
	 *
	 * @since 1.2.1
	 *
	 * @return int|null
	 */
	public function get_selected_location() {

		return $this->get_active_filter( 'location_id' );
	}

	/**
	 * Return search filter values or null.
	 *
	 * @since 1.0.0
	 *
	 * @return string|null
	 */
	public function get_search_term() {

		return $this->get_active_filter( 's' );
	}

	/**
	 * Get the filter label map for the search results notice.
	 *
	 * @since 1.4.0
	 *
	 * @return array Filter label map.
	 */
	protected function get_filter_label_map() {

		return array_merge(
			parent::get_filter_label_map(),
			[
				'tags'        => [
					'label' => esc_html__( 'Tag', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
					'model' => TagModel::class,
				],
				'location_id' => [
					'label' => esc_html__( 'Location', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
					'model' => LocationModel::class,
				],
			]
		);
	}

	/**
	 * Get the service statuses for filtering purpose.
	 *
	 * @since 1.0.0
	 *
	 * @return array Associative array of service statuses.
	 */
	private function get_statuses() {

		// In this order statuses will appear in filters bar.
		$statuses = [
			'all'      => esc_html__( 'All', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
			'enabled'  => esc_html__( 'Enabled', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
			'disabled' => esc_html__( 'Disabled', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
			'trash'    => esc_html__( 'Trash', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
		];

		return $statuses;
	}

	/**
	 * Get the items counts for various statuses of services.
	 *
	 * @since 1.0.0
	 */
	private function get_counts() {

		$this->counts = [];

		// Base params with applied filters.
		$filters = $this->get_applied_filters();

		unset( $filters['status'] );

		$query = ( new QueryBuilder() )
			->table( 'services' )
			->select( [ 'services.status', 'COUNT(DISTINCT id)' ] )
			->group_by( [ 'services.status' ] );

		$this->build_filters_query( $query, $filters );

		$stats = $query->get();

		$stats = array_combine( array_column( $stats, 'status' ), array_column( $stats, 'count' ) );

		$this->counts['total'] = array_sum( $stats ) - ( $stats['trash'] ?? 0 );

		foreach ( $this->get_statuses() as $status => $name ) {
			$count = $stats[ $status ] ?? 0;

			$this->counts[ 'status_' . $status ] = $count;
		}
	}

	/**
	 * Get the data, prepare pagination, process bulk actions.
	 * Prepare columns for display.
	 *
	 * @since 1.0.0
	 * @since 1.2.0 Decouple service model relations.
	 */
	public function prepare_items() {

		// Retrieve count.
		$this->get_counts();

		$items_per_page = 20;
		$offset         = ( $this->get_pagenum() - 1 ) * $items_per_page;
		$orderby        = 'id';
		$order          = 'DESC';
		$filters        = $this->get_applied_filters();

		// phpcs:disable WordPress.Security.NonceVerification.Recommended
		if (
			! empty( $_REQUEST['orderby'] ) &&
			in_array( $_REQUEST['orderby'], array_keys( $this->get_sortable_columns() ), true )
		) {
			$orderby = sanitize_key( $_REQUEST['orderby'] );
		}

		if (
			! empty( $_REQUEST['order'] ) &&
			in_array( strtoupper( $_REQUEST['order'] ), [ 'ASC', 'DESC' ], true )
		) {
			$order = sanitize_key( $_REQUEST['order'] );
		}
		// phpcs:enable WordPress.Security.NonceVerification.Recommended

		$query = ServiceModel::query()
			->with(
				$this->get_service_model_relations()
			);

		$this->build_filters_query( $query, $filters );

		$query->order_by( 'services.' . $orderby, $order )
			->offset( $offset )
			->limit( $items_per_page )
			->group_by( [ 'services.id' ] );

		// Exclude trash services from 'all' view.
		if ( empty( $filters['status'] ) ) {
			$query->where( 'services.status', '!=', 'trash', '%s' );
		}

		$this->items = $query->get();

		$total_items = $this->counts['total'];

		if (
			! empty( $filters['status'] ) &&
			isset( $this->counts[ 'status_' . $filters['status'] ] )
		) {
			$total_items = $this->counts[ 'status_' . $filters['status'] ];
		}

		$this->set_pagination_args(
			[
				'total_items' => $total_items,
				'per_page'    => $items_per_page,
			]
		);
	}

	/**
	 * Get the service model relations.
	 *
	 * @since 1.2.0
	 *
	 * @return array
	 */
	protected function get_service_model_relations() {

		return [
			'meta'      => fn( $q ) => $q->where( 'meta_key', 'IN', [ 'online_locations' ], '%s' ),
			'tags'      => fn( $q ) => $q->select( [ 'id', 'name' ] ),
			'locations' => fn( $q ) => $q->select( [ 'id', 'name' ] ),
		];
	}

	/**
	 * Whether the table has items to display or not.
	 *
	 * @since 1.0.0
	 *
	 * @return bool
	 */
	public function has_items() {

		return count( $this->items ) > 0;
	}

	/**
	 * Message to be displayed when there are no items.
	 *
	 * @since 1.0.0
	 */
	public function no_items() {

		if ( ! empty( $this->get_applied_filters() ) ) {
			esc_html_e( 'No services found.', 'sugar-calendar-bookings-scheduling-appointments-lite' );
		} else {
			?>
			<div class="wp-list-table__no-items">
				<div class="wp-list-table__no-items-icon">
					<img src="<?php echo esc_url( SCBOOKINGS_PLUGIN_URL . 'assets/images/icons/briefcase.svg' ); ?>">
				</div>
				<div class="wp-list-table__no-items-title"><?php esc_html_e( 'Add your First Service!', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?></div>
				<?php
				// TODO: Add plus icon.
				UI::button(
					[
						'type'  => 'tertiary',
						'size'  => 'sm',
						'text'  => esc_html__( 'Add New', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
						'class' => 'sugar-calendar-bookings-btn-new-item',
					]
				);
				?>
			</div>
			<?php
		}
	}

	/**
	 * Extra controls to be displayed between bulk actions and pagination.
	 *
	 * @since 1.0.0
	 *
	 * @param string $which Which tablenav: top or bottom.
	 */
	protected function extra_tablenav( $which ) {

		if ( $which !== 'top' ) {
			return;
		}
		?>
		<div class="alignleft actions">
			<?php $this->display_table_filters(); ?>
			<?php submit_button( esc_html__( 'Filter', 'sugar-calendar-bookings-scheduling-appointments-lite' ), '', 'filter_action', false ); ?>
		</div>
		<?php
	}

	/**
	 * Display table filters.
	 *
	 * @since 1.2.0
	 *
	 * @return void
	 */
	private function display_table_filters() {

		$selected_filters = $this->get_selected_filters();
		$table_filters    = $this->get_table_filters();

		foreach ( $table_filters as $key => $filter ) {
			?>
			<select name="<?php echo esc_attr( $key ); ?>">
				<option value=""><?php echo esc_html( $filter['default'] ); ?></option>
				<?php foreach ( $filter['options'] as $option ) : ?>
					<?php
					$selected_value = $selected_filters[ $key ] ?? null;
					$is_selected    = is_array( $selected_value )
						? selected( in_array( $option->id, $selected_value ?? [], true ) )
						: selected( $selected_value, $option->id, false );
					?>
					<option value="<?php echo esc_attr( $option->id ); ?>" <?php echo esc_attr( $is_selected ); ?>><?php echo esc_html( $option->name ); ?></option>
				<?php endforeach; ?>
			</select>
			<?php
		}
	}

	/**
	 * Get the selected filters.
	 *
	 * @since 1.2.0
	 *
	 * @return array
	 */
	protected function get_selected_filters() {

		return [
			'tags'        => $this->get_selected_tags(),
			'location_id' => $this->get_selected_location(),
		];
	}

	/**
	 * Get the table filters.
	 *
	 * @since 1.2.0
	 *
	 * @return array
	 */
	protected function get_table_filters() {

		// TODO: Get only tags that are associated with available services.
		$tags = TagModel::query()
			->where( 'object_type', '=', 'service', '%s' )
			->get();

		$locations = LocationModel::query()
			->select( [ 'id', 'name' ] )
			->order_by( 'name' )
			->get();

		return [
			'tags'        => [
				'default' => esc_html__( 'All Tags', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
				'options' => $tags,
			],
			'location_id' => [
				'default' => esc_html__( 'All Locations', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
				'options' => $locations,
			],
		];
	}

	/**
	 * Get the name of the primary column.
	 * Important for the mobile view.
	 *
	 * @since 1.0.0
	 *
	 * @return string The name of the primary column.
	 */
	protected function get_primary_column_name() {

		return 'service';
	}

	/**
	 * Allow users to select multiple services at once (to perform a bulk action, for example).
	 *
	 * @since 1.0.0
	 *
	 * @param ServiceModel $service ServiceModel object.
	 *
	 * @return string Checkbox for bulk selection.
	 */
	protected function column_cb( $service ) {

		return sprintf(
			'<input type="checkbox" name="service_id[]" value="%d" />',
			$service->get_id()
		);
	}

	/**
	 * Render the Service Name column.
	 *
	 * @since 1.0.0
	 *
	 * @param ServiceModel $service The current row service.
	 *
	 * @return string
	 */
	protected function column_service( $service ) {

		$title = '<strong><i style="color: ' . $service->color . '"></i><a href="#" data-service-id="' . esc_attr( $service->id ) . '" class="row-title">' . esc_html( $service->name ) . '</a></strong>';

		$actions = [];

		// TODO: Add duplicate, view actions.

		if ( current_user_can( 'manage_options' ) ) {
			if ( $this->get_selected_status() !== 'trash' ) {
				$actions['edit'] = esc_html__( 'Edit', 'sugar-calendar-bookings-scheduling-appointments-lite' );

				if ( $service->status === 'enabled' ) {
					$actions['share'] = esc_html__( 'Share', 'sugar-calendar-bookings-scheduling-appointments-lite' );
				}

				$actions['trash'] = esc_html__( 'Trash', 'sugar-calendar-bookings-scheduling-appointments-lite' );
			} else {
				$actions['restore'] = esc_html__( 'Restore', 'sugar-calendar-bookings-scheduling-appointments-lite' );
				$actions['delete']  = esc_html__( 'Delete', 'sugar-calendar-bookings-scheduling-appointments-lite' );
			}
		}

		$actions = array_combine(
			array_keys( $actions ),
			array_map( fn( $label, $action ) => sprintf( '<a href="%1$s">%2$s</a>', $this->get_quick_action_url( $service, $action ), $label ), $actions, array_keys( $actions ) )
		);

		return sprintf( '%1$s %2$s', $title, $this->row_actions( $actions ) );
	}

	/**
	 * Get the link to a certain action: "edit" or "delete" for row actions.
	 *
	 * @since 1.0.0
	 *
	 * @param ServiceModel $service ServiceModel object.
	 * @param string       $link    The link type to create.
	 *
	 * @return string
	 */
	private function get_quick_action_url( $service, $link = 'edit' ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		$url  = '';
		$link = sanitize_key( $link );

		switch ( $link ) {
			case 'edit':
				$url = '#';
				break;

			case 'share':
				$url = '#';
				break;

			case 'trash':
				$url = esc_url(
					wp_nonce_url(
						add_query_arg(
							[
								'action'     => 'trash',
								'service_id' => $service->id,
							],
							$this->get_base_url()
						),
						'bulk-services'
					)
				);
				break;

			case 'restore':
				$url = esc_url(
					wp_nonce_url(
						add_query_arg(
							[
								'action'     => 'restore',
								'service_id' => $service->id,
							],
							$this->get_base_url()
						),
						'bulk-services'
					)
				);
				break;

			case 'delete':
				$url = esc_url(
					wp_nonce_url(
						add_query_arg(
							[
								'action'     => 'delete',
								'service_id' => $service->id,
							],
							$this->get_base_url()
						),
						'bulk-services'
					)
				);
				break;
		}

		return $url;
	}

	/**
	 * Render the Tags column.
	 *
	 * @since 1.0.0
	 *
	 * @param ServiceModel $service The current row service.
	 *
	 * @return string
	 */
	protected function column_tags( $service ) {

		$links = array_map(
			function ( $tag ) {

				// phpcs:ignore WPForms.Formatting.EmptyLineBeforeReturn.RemoveEmptyLineBeforeReturnStatement
				return sprintf(
					'<a href="%1$s">%2$s</a>',
					esc_url( add_query_arg( 'tags', $tag->id, $this->get_base_url() ) ),
					esc_html( $tag->name )
				);
			},
			$service->tags
		);

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

	/**
	 * Render the Duration column.
	 *
	 * @since 1.0.0
	 *
	 * @param ServiceModel $service The current row service.
	 *
	 * @return string
	 */
	protected function column_duration( $service ) {

		return Time::human_readable_duration( $service->duration );
	}

	/**
	 * Render the Price column.
	 *
	 * @since 1.0.0
	 *
	 * @param ServiceModel $service The current row service.
	 *
	 * @return string
	 */
	protected function column_price( $service ) {

		return PaymentsHelpers::format_currency( $service->price );
	}

	/**
	 * Render the Location column.
	 *
	 * @since 1.0.0
	 *
	 * @param ServiceModel $service The current row service.
	 *
	 * @return string
	 */
	protected function column_location( $service ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		$in_person_locations = $service->locations;
		$online_locations    = $service->online_locations;

		if ( empty( $in_person_locations ) && empty( $online_locations ) ) {
			return '-';
		}

		if ( ! empty( $in_person_locations ) ) {
			$in_person_locations = array_map(
				fn( $location ) => sprintf( '<a href="#" data-location-id="%1$s">%2$s</a>', esc_attr( $location->id ), esc_html( $location->name ) ),
				$in_person_locations
			);
		}

		if ( ! empty( $online_locations ) ) {
			$online_locations = array_map(
				function ( $location ) {

					// Zoom locations have no URL at service level — display provider name.
					if ( empty( $location['url'] ) ) {
						$provider = OnlineLocationProviderFactory::from_array( $location );

						return esc_html( $provider ? $provider->get_display_name() : ( $location['provider'] ?? '' ) );
					}

					$location_name = wp_trim_words( $location['url'], 30 );
					$location_url  = $location['url'];

					return sprintf( '<a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s</a>', esc_url( $location_url ), esc_html( $location_name ) );
				},
				$online_locations
			);
		}

		$locations = array_merge( $in_person_locations, $online_locations );

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

	/**
	 * Render the Status column.
	 *
	 * @since 1.0.0
	 *
	 * @param ServiceModel $service The current row service.
	 *
	 * @return string
	 */
	protected function column_status( $service ) {

		return '<span class="status-badge ' . $service->status . '">' . $service->get_status_label() . '</span>';
	}

	/**
	 * Define a list of available bulk actions.
	 *
	 * @since 1.0.0
	 *
	 * @return array List of actions: slug=>name.
	 */
	protected function get_bulk_actions() {

		$actions = [];

		if ( current_user_can( 'manage_options' ) ) {
			if ( $this->get_selected_status() !== 'trash' ) {
				$actions['enable']  = esc_html__( 'Enable', 'sugar-calendar-bookings-scheduling-appointments-lite' );
				$actions['disable'] = esc_html__( 'Disable', 'sugar-calendar-bookings-scheduling-appointments-lite' );
				$actions['trash']   = esc_html__( 'Trash', 'sugar-calendar-bookings-scheduling-appointments-lite' );
			} else {
				$actions['restore'] = esc_html__( 'Restore', 'sugar-calendar-bookings-scheduling-appointments-lite' );
				$actions['delete']  = esc_html__( 'Delete', 'sugar-calendar-bookings-scheduling-appointments-lite' );
			}
		}

		return $actions;
	}

	/**
	 * Process the bulk actions.
	 *
	 * @since 1.0.0
	 */
	public function process_bulk_actions() {

		$this->display_bulk_action_message();

		if ( empty( $_REQUEST['_wpnonce'] ) ) {
			return;
		}

		if ( ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'bulk-services' ) ) {
			return;
		}

		$this->process_bulk_action_single();
	}

	/**
	 * Display a bulk action result message.
	 *
	 * @since 1.0.0
	 */
	private function display_bulk_action_message() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity

		$notices = Plugin::get( Notices::class );

		if ( ! $notices->get_query_notice() ) {
			return;
		}

		$message = '';
		$count   = (int) ( $notices->get_query_notice_data()[0] ?? 0 );

		switch ( $notices->get_query_notice() ) {
			case 'bulk_enabled':
				/* translators: %d - number of marked as enabled services. */
				$message = _n( '%d service was successfully marked as enabled.', '%d services were successfully marked as enabled.', $count, 'sugar-calendar-bookings-scheduling-appointments-lite' );
				break;

			case 'bulk_disabled':
				/* translators: %d - number of marked as disabled services. */
				$message = _n( '%d service was successfully marked as disabled.', '%d services were successfully marked as disabled.', $count, 'sugar-calendar-bookings-scheduling-appointments-lite' );
				break;

			case 'bulk_trashed':
				/* translators: %d - number of trashed services. */
				$message = _n( '%d service was successfully trashed.', '%d services were successfully trashed.', $count, 'sugar-calendar-bookings-scheduling-appointments-lite' );
				break;

			case 'bulk_restored':
				/* translators: %d - number of restored services. */
				$message = _n( '%d service was successfully restored.', '%d services were successfully restored.', $count, 'sugar-calendar-bookings-scheduling-appointments-lite' );
				break;

			case 'bulk_deleted':
				/* translators: %d - number of deleted services. */
				$message = _n( '%d service was successfully deleted.', '%d services were successfully deleted.', $count, 'sugar-calendar-bookings-scheduling-appointments-lite' );
				break;
		}

		if ( ! empty( $message ) ) {
			Plugin::get( Notices::class )->success( sprintf( $message, $count ) );
		}
	}

	/**
	 * Process single bulk action.
	 *
	 * @since 1.0.0
	 */
	private function process_bulk_action_single() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.CyclomaticComplexity.TooHigh

		$action = $this->current_action();

		if ( empty( $action ) ) {
			return;
		}

		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Recommended
		$ids = isset( $_GET['service_id'] ) ? array_map( 'absint', (array) wp_unslash( $_GET['service_id'] ) ) : false;

		if ( empty( $ids ) ) {
			return;
		}

		$sendback = $this->get_base_url();

		switch ( $action ) {
			// Mark as enabled.
			case 'enable':
				$sendback = $this->process_bulk_action_single_enabled( $ids, $sendback );
				break;

			// Mark as disabled.
			case 'disable':
				$sendback = $this->process_bulk_action_single_disabled( $ids, $sendback );
				break;

			// Trash services.
			case 'trash':
				$sendback = $this->process_bulk_action_single_trash( $ids, $sendback );
				break;

			// Delete services.
			case 'delete':
				$sendback = $this->process_bulk_action_single_delete( $ids, $sendback );
				break;

			// Restore services.
			case 'restore':
				$sendback = $this->process_bulk_action_single_restore( $ids, $sendback );
				break;
		}

		wp_safe_redirect( $sendback );
		exit();
	}

	/**
	 * Process the bulk action mark as enabled.
	 *
	 * @since 1.0.0
	 *
	 * @param array  $ids      IDs to process.
	 * @param string $sendback URL query string.
	 *
	 * @return string
	 */
	private function process_bulk_action_single_enabled( $ids, $sendback ) {

		$enabled  = 0;
		$services = ServiceModel::query()
			->where( 'id', 'IN', $ids, '%d' )
			->get();

		foreach ( $services as $service ) {
			$service->status = 'enabled';

			$success = $service->save();

			// If it didn't work, continue.
			if ( ! $success ) {
				continue;
			}

			++$enabled;
		}

		return add_query_arg(
			[
				'scbookings-notice'      => 'bulk_enabled',
				'scbookings-notice-data' => $enabled,
			],
			$sendback
		);
	}

	/**
	 * Process the bulk action mark as disabled.
	 *
	 * @since 1.0.0
	 *
	 * @param array  $ids      IDs to process.
	 * @param string $sendback URL query string.
	 *
	 * @return string
	 */
	private function process_bulk_action_single_disabled( $ids, $sendback ) {

		$disabled = 0;
		$services = ServiceModel::query()
			->where( 'id', 'IN', $ids, '%d' )
			->get();

		foreach ( $services as $service ) {
			$service->status = 'disabled';

			$success = $service->save();

			// If it didn't work, continue.
			if ( ! $success ) {
				continue;
			}

			++$disabled;
		}

		return add_query_arg(
			[
				'scbookings-notice'      => 'bulk_disabled',
				'scbookings-notice-data' => $disabled,
			],
			$sendback
		);
	}

	/**
	 * Process the bulk action trash.
	 *
	 * @since 1.0.0
	 *
	 * @param array  $ids      IDs to process.
	 * @param string $sendback URL query string.
	 *
	 * @return string
	 */
	private function process_bulk_action_single_trash( $ids, $sendback ) {

		$trashed  = 0;
		$services = ServiceModel::query()
			->with( [ 'appointments' ] )
			->where( 'id', 'IN', $ids, '%d' )
			->get();

		foreach ( $services as $service ) {
			$service->prev_status = $service->status;
			$service->status      = 'trash';

			$success = $service->save();

			// If it didn't work, continue.
			if ( ! $success ) {
				continue;
			}

			++$trashed;
		}

		return add_query_arg(
			[
				'scbookings-notice'      => 'bulk_trashed',
				'scbookings-notice-data' => $trashed,
			],
			$sendback
		);
	}

	/**
	 * Process the bulk action restores.
	 *
	 * @since 1.0.0
	 *
	 * @param array  $ids      IDs to process.
	 * @param string $sendback URL query string.
	 *
	 * @return string
	 */
	private function process_bulk_action_single_restore( $ids, $sendback ) {

		$restored = 0;
		$services = ServiceModel::query()
			->with( [ 'meta' ] )
			->where( 'id', 'IN', $ids, '%d' )
			->get();

		foreach ( $services as $service ) {
			$service->status      = $service->prev_status ?? 'enabled';
			$service->prev_status = null;

			$success = $service->save();

			// If it didn't work, continue.
			if ( ! $success ) {
				continue;
			}

			++$restored;
		}

		$trash_count = ServiceModel::query()
			->where( 'status', '=', 'trash', '%s' )
			->count();

		// If trash is emptied.
		if ( $trash_count < 1 ) {
			$sendback = remove_query_arg( 'status', $sendback );
		}

		return add_query_arg(
			[
				'scbookings-notice'      => 'bulk_restored',
				'scbookings-notice-data' => $restored,
			],
			$sendback
		);
	}

	/**
	 * Process the bulk action delete.
	 *
	 * @since 1.0.0
	 *
	 * @param array  $ids      IDs to process.
	 * @param string $sendback URL query string.
	 *
	 * @return string
	 */
	private function process_bulk_action_single_delete( $ids, $sendback ) {

		Plugin::get( DeleteManager::class )->delete_services( $ids );

		return add_query_arg(
			[
				'scbookings-notice'      => 'bulk_deleted',
				'scbookings-notice-data' => count( $ids ),
			],
			$sendback
		);
	}
}
