<?php
/**
 * Defines Segments page functionality
 *
 * @link https://personalizewp.com/
 * @since 3.2.0
 *
 * @package PersonalizeWP
 * @subpackage PersonalizeWP/Admin
 */

namespace PersonalizeWP\Admin;

use PersonalizeWP\Admin\Admin;
use PersonalizeWP\Admin\Admin_Base_Page;
use PersonalizeWP\Model\Segment;

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

/**
 * Defines Segments page functionality
 */
class PageSegments extends Admin_Base_Page {

	/**
	 * Slug for segments admin page
	 */
	const SLUG = 'personalizewp/segments';

	/**
	 * URL for main segments admin page (listing)
	 *
	 * @var string
	 */
	public static $index_url = 'admin.php?page=' . self::SLUG;

	/**
	 * URL for create segment page
	 *
	 * @var string
	 */
	public static $create_url = 'admin.php?page=' . self::SLUG . '&' . Admin::URL_ACTION . '=create';

	/**
	 * URL for edit segment page
	 *
	 * @var string
	 */
	public static $edit_url = 'admin.php?page=' . self::SLUG . '&' . Admin::URL_ACTION . '=edit&id=';

	/**
	 * URL for view segment page
	 *
	 * @var string
	 */
	public static $view_url = 'admin.php?page=' . self::SLUG . '&' . Admin::URL_ACTION . '=view&id=';

	/**
	 * URL segment activation
	 *
	 * @var string
	 */
	public static $activate_url = 'admin.php?page=' . self::SLUG . '&' . Admin::URL_ACTION . '=activate&id=';

	/**
	 * URL segment deactivation
	 *
	 * @var string
	 */
	public static $deactivate_url = 'admin.php?page=' . self::SLUG . '&' . Admin::URL_ACTION . '=deactivate&id=';

	/**
	 * URL for deleting a segment
	 *
	 * @var string
	 */
	public static $delete_url = 'admin.php?page=' . self::SLUG . '&' . Admin::URL_ACTION . '=delete&id=';

	/**
	 * Store the path for Segments templates
	 *
	 * @var string
	 */
	public $template_path;

	/**
	 * Used to store the currently processing form data, shared between process/route actions
	 *
	 * @var array
	 */
	protected $form_post = [];

	/**
	 * Constructor
	 */
	protected function __construct() {
		parent::__construct();

		$this->page_title    = __( 'Segments', 'personalizewp' );
		$this->template_path = plugin_dir_path( __DIR__ ) . 'admin/partials/segments/';

		$this->current_action = isset( $_REQUEST[ Admin::URL_ACTION ] ) ? sanitize_text_field( wp_unslash( $_REQUEST[ Admin::URL_ACTION ] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: Form verified within each process
		$this->current_page   = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: Form verified within each process

		add_action( 'admin_init', [ $this, 'process' ] );
		add_filter( 'set-screen-option', [ $this, 'set_screen_options' ], 10, 3 );
	}

	/**
	 * Display the Dashboard screen options.
	 */
	public function dashboard_screen_options() {
		$screen = get_current_screen();

		$is_screen = is_object( $screen ) && str_ends_with( $screen->id, self::SLUG );
		if ( ! $is_screen ) {
			return;
		}
		// Re-trigger core WP, as admins' call runs way too early IMHO.
		set_screen_options();

		$args = array(
			'label'   => __( 'Number of Segments per page:', 'personalizewp' ),
			'default' => 20,
			'option'  => 'edit_pwp_segments_per_page',
		);
		add_screen_option( 'per_page', $args );
	}

	/**
	 * Store the screen options.
	 *
	 * @param mixed  $screen_option The value to save instead of the option value.
	 *                              Default false (to skip saving the current option).
	 * @param string $option        The option name.
	 * @param int    $value         The option value.
	 */
	public function set_screen_options( $screen_option, $option, $value ) {
		if ( 'edit_pwp_segments_per_page' === $option ) {
			return $value;
		}
		return $screen_option;
	}

	/**
	 * Process the current Segment request, typically POST/GET, followed by a redirect.
	 *
	 * @return void
	 */
	public function process() {
		// Double check for user caps before any possible processing of data.
		if ( ! current_user_can( Admin::CAP ) ) {
			return;
		}

		if ( self::SLUG !== $this->current_page ) {
			return;
		}

		$segment_id = (int) isset( $_REQUEST['id'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['id'] ) ) : false;

		if ( ! empty( $_POST['pwp_form'] ) ) {
			// POST specific actions
			switch ( $this->current_action ) {
				case 'create':
					check_admin_referer( 'create-segment' ); // Immediately dies on failing.

					$this->form_post = filter_input( INPUT_POST, 'pwp_form', \FILTER_CALLBACK, [ 'options' => 'sanitize_text_field' ] );
					$this->create_action( $this->form_post );
					break;

				case 'edit':
					if ( empty( $segment_id ) ) {
						wp_safe_redirect( admin_url( self::$index_url ) );
						die();
					}
					check_admin_referer( 'edit-segment-' . $segment_id ); // Immediately dies on failing.

					$this->form_post = filter_input( INPUT_POST, 'pwp_form', \FILTER_CALLBACK, [ 'options' => 'sanitize_text_field' ] );
					$this->edit_action( $segment_id, $this->form_post );
					break;
			}
		}

		// GET Specific actions
		switch ( $this->current_action ) {
			case 'activate':
				if ( empty( $segment_id ) ) {
					wp_safe_redirect( admin_url( self::$index_url ) );
					die();
				}
				check_admin_referer( 'activate-segment-' . $segment_id ); // Immediately dies on failing.

				$this->activate_action( $segment_id );
				break;

			case 'deactivate':
				if ( empty( $segment_id ) ) {
					wp_safe_redirect( admin_url( self::$index_url ) );
					die();
				}
				check_admin_referer( 'deactivate-segment-' . $segment_id ); // Immediately dies on failing.

				$this->deactivate_action( $segment_id );
				break;

			case 'delete':
				if ( empty( $segment_id ) ) {
					wp_safe_redirect( admin_url( self::$index_url ) );
					die();
				}
				check_admin_referer( 'delete-segment-' . $segment_id ); // Immediately dies on failing.

				$this->delete_action( $segment_id );
				break;
		}
	}

	/**
	 * Route to the correct screen
	 *
	 * @return void
	 */
	public function route() {
		// Double check for user caps before any possible processing of data.
		if ( ! current_user_can( Admin::CAP ) ) {
			return;
		}

		if ( self::SLUG !== $this->current_page ) {
			return;
		}

		$segment_id = (int) isset( $_REQUEST['id'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['id'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Not used to change data

		switch ( $this->current_action ) {
			case 'create':
				$this->create_screen( $this->form_post );
				break;

			case 'edit':
				if ( empty( $segment_id ) ) {
					wp_safe_redirect( admin_url( self::$index_url ) );
					die();
				}

				$this->edit_screen( $segment_id, $this->form_post );
				break;

			case 'view':
				if ( empty( $segment_id ) ) {
					wp_safe_redirect( admin_url( self::$index_url ) );
					die();
				}

				$this->view_screen( $segment_id );
				break;

			case 'index':
			default:
				$this->index_screen();
				break;
		}
	}

	/**
	 * Displays Segments listing screen
	 *
	 * @return void
	 */
	protected function index_screen() {

		$status_message = '';
		// Trigger messaging from actions.
		if ( isset( $_REQUEST['created'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- this is only displaying a message with no dynamic values.
			$status_message = __( 'Segment created', 'personalizewp' );
		}
		if ( isset( $_REQUEST['updated'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- this is only displaying a message with no dynamic values.
			$status_message = __( 'Segment updated', 'personalizewp' );
		}
		if ( isset( $_REQUEST['activated'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- this is only displaying a message with no dynamic values.
			$status_message = __( 'Segment activated', 'personalizewp' );
		}
		if ( isset( $_REQUEST['deactivated'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- this is only displaying a message with no dynamic values.
			$status_message = __( 'Segment deactivated', 'personalizewp' );
		}
		if ( isset( $_REQUEST['deleted'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- this is only displaying a message with no dynamic values.
			$status_message = __( 'Segment deleted', 'personalizewp' );
		}
		if ( $status_message ) {
			wp_admin_notice(
				'<span class="dashicons dashicons-yes-alt"></span>' . $status_message,
				array(
					'type'        => 'success',
					'id'          => 'message',
					'dismissible' => true,
				)
			);
		}

		$this->display_header();
		require_once $this->template_path . 'index.php';
		$this->display_footer();
	}

	/**
	 * View singular Segment screen
	 *
	 * @param int $segment_id Segment ID
	 *
	 * @return void
	 */
	protected function view_screen( $segment_id ) {

		$segment = Segment::find( $segment_id );

		if ( ! $segment ) {
			$this->showError( __( 'You attempted to edit an item that does not exist. Perhaps it was deleted?', 'personalizewp' ) );
		}

		// Add filters to restrict Contacts to those within this Segment.
		add_filter( 'personalizewp_contacts_row_actions', [ $this, 'filter_contacts_actions' ], 10, 2 );
		add_filter( 'personalizewp_contacts_clauses_request', [ $this, 'filter_contacts_clauses' ], 10, 2 );
		add_action( 'personalizewp_contacts_search_fields', [ $this, 'add_contacts_search_fields' ] );

		$this->display_header();
		require_once $this->template_path . 'view.php';
		$this->display_footer();
	}

	/**
	 * Display Segment creation screen
	 *
	 * @param array $data Form data
	 *
	 * @return void
	 */
	protected function create_screen( $data = [] ) {

		// Mock an empty Segment to start creation, or fill with existing form data
		if ( ! empty( $data ) ) {
			unset( $data['ID'] );
		}
		$segment = new Segment( $data );

		$this->display_header();
		require_once $this->template_path . 'create.php';
		$this->display_footer();
	}

	/**
	 * Display Segment edit screen
	 *
	 * @param int   $segment_id Segment ID
	 * @param array $data       Form data
	 *
	 * @return void
	 */
	protected function edit_screen( $segment_id, $data = [] ) {

		$segment = Segment::find( $segment_id );

		if ( ! $segment ) {
			$this->showError( __( 'You attempted to edit an item that does not exist. Perhaps it was deleted?', 'personalizewp' ) );
		}

		// Re-populate with changes if set
		if ( ! empty( $data ) ) {
			$data['ID'] = $segment_id;
			$segment    = new Segment( $data );
		}

		$this->display_header();
		require_once $this->template_path . 'edit.php';
		$this->display_footer();
	}

	/**
	 * Activate Segment
	 *
	 * @param int $segment_id Segment ID
	 *
	 * @return void
	 */
	protected function activate_action( $segment_id ) {

		Segment::activate( $segment_id );

		wp_safe_redirect( add_query_arg( array( 'activated' => 1 ), admin_url( self::$index_url ) ) );
		exit;
	}

	/**
	 * Deactivate Segment
	 *
	 * @param int $segment_id Segment ID
	 *
	 * @return void
	 */
	protected function deactivate_action( $segment_id ) {

		Segment::deactivate( $segment_id );

		wp_safe_redirect( add_query_arg( array( 'deactivated' => 1 ), admin_url( self::$index_url ) ) );
		exit;
	}

	/**
	 * Create Segment
	 *
	 * @param array $data Form data
	 *
	 * @return void
	 */
	protected function create_action( $data = [] ) {

		$valid_data = $this->validate_sanitize( $data );
		if ( false !== $valid_data ) {
			$segment = new Segment( $valid_data );
			$segment->save();

			wp_safe_redirect( add_query_arg( array( 'created' => 1 ), admin_url( self::$index_url ) ) );
			exit;
		}
	}

	/**
	 * Save Segment changes
	 *
	 * @param int   $segment_id Segment ID
	 * @param array $data       Form data
	 *
	 * @return void
	 */
	protected function edit_action( $segment_id, $data = [] ) {

		$segment = Segment::find( $segment_id );

		if ( ! $segment ) {
			$this->showError( __( 'You attempted to edit an item that does not exist. Perhaps it was deleted?', 'personalizewp' ) );
		}

		$valid_data = $this->validate_sanitize( $data, $segment );
		if ( false !== $valid_data ) {
			$segment->update( $valid_data );

			wp_safe_redirect( add_query_arg( array( 'updated' => 1 ), admin_url( self::$index_url ) ) );
			exit;
		}
	}

	/**
	 * Delete Segment
	 *
	 * @param int $segment_id Segment ID
	 *
	 * @return void
	 */
	protected function delete_action( $segment_id ) {

		// Deleting the Segment automatically deletes the Contacts relationships
		$result = Segment::delete( $segment_id );
		if ( ! $result ) {
			// Nothing to do
			wp_safe_redirect( admin_url( self::$index_url ) );
			exit;
		}

		wp_safe_redirect( add_query_arg( array( 'deleted' => 1 ), admin_url( self::$index_url ) ) );
		exit;
	}

	/**
	 * Validate submitted form data for Segments
	 *
	 * Sets validation errors on missing data, and sets up the 'conditions' for storing to the db.
	 *
	 * @since 1.1.0 Added lead_score validation
	 *
	 * @param  array         $data    Form data
	 * @param  Segment|false $segment Existing Segment if editing
	 *
	 * @uses addValidationError()
	 *
	 * @return array|false  Return sanitized data if valid, false otherwise
	 */
	protected function validate_sanitize( $data, $segment = false ) {

		$invalid    = false;
		$valid_data = [];

		if ( empty( $data ) ) {
			$this->addValidationError( 'form', __( 'Form is not valid', 'personalizewp' ) );
			return false;
		}

		if ( $segment instanceof Segment &&
			( empty( $data['id'] ) || $segment->ID !== (int) $data['id'] )
			) {
			$this->addValidationError( 'form', __( 'ID is required to update a segment', 'personalizewp' ) );
			$invalid = true;
		} elseif ( $segment instanceof Segment && $segment->ID === (int) $data['id'] ) {
			$valid_data['id'] = $segment->ID;
		}

		if ( empty( $data['name'] ) ) {
			$this->addValidationError( 'name', __( 'Please enter a name', 'personalizewp' ) );
			$invalid = true;
		} else {
			$valid_data['name'] = $data['name'];
		}

		if ( empty( $data['type'] ) ) {
			$this->addValidationError( 'type', __( 'Please select a type', 'personalizewp' ) );
			$invalid = true;
			// Immediately return as type is so fundamental.
			return false;
		} else {
			$valid_data['type'] = $data['type'];
		}

		$valid_data['is_active'] = isset( $data['is_active'] ) ? (bool) $data['is_active'] : false;

		$valid_operators        = [ 'ANY', 'ALL' ];
		$is_valid_operator      = ( ! empty( $data['operator'] ) && in_array( $data['operator'], $valid_operators, true ) );
		$valid_data['operator'] = $is_valid_operator ? $data['operator'] : Segment::$default_operator;

		// Clear out/setup conditions to store
		$conditions = '';

		// Validate/sanitize all Conditions
		switch ( $data['type'] ) {
			case 'lead_score':
				// Sanitize/Validate lead scores
				if ( isset( $data['lead_score']['min'] ) ) {
					$min = (int) $data['lead_score']['min'];
					$max = (int) $data['lead_score']['max'];

					if ( empty( $min ) ) {
						$this->addValidationError( 'conditions', __( 'The minimum lead score needs to be a non-zero number', 'personalizewp' ) );
						$invalid = true;
						break;
					}
					// If a maximum is set, it must always be greater than the minimum, even when negative
					if ( 0 !== $min && ! empty( $max ) && $max < $min ) {
						$this->addValidationError( 'conditions', __( 'The maximum score must be greater than the minimum score.', 'personalizewp' ) );
						$invalid = true;
						break;
					}

					$conditions = compact( 'min', 'max' );
				}
				break;

			case 'location':
				// Sanitize/Validate locations
				if ( ! empty( $data['location'] ) ) {
					$conditions       = [];
					$data['location'] = array_map( 'strtoupper', $data['location'] );
					$valid_locations  = \personalizewp()->get_countries();
					foreach ( $data['location'] as $i => $row ) {
						if ( ! array_key_exists( $row, $valid_locations ) ) {
							$row = '';
						} else {
							$conditions[] = $row;
						}
						if ( empty( $row ) ) {
							$this->addValidationError( 'conditions', __( 'A valid location needs to be selected', 'personalizewp' ) );
							$invalid = true;
						}
					}
				}
				break;

			case 'post_type':
				// Sanitize/Validate post_type
				if ( ! empty( $data['post_type'] ) ) {
					$conditions        = [];
					$data['post_type'] = array_unique( array_filter( array_map( 'strtolower', $data['post_type'] ) ) );

					// Compare against known post types, only need the names.
					$valid_post_types = get_post_types( array( 'public' => true ), 'names' );
					foreach ( $data['post_type'] as $row ) {
						if ( ! isset( $valid_post_types[ $row ] ) ) {
							$this->addValidationError( 'conditions', __( 'A valid Post Type needs to be selected', 'personalizewp' ) );
							$invalid = true;
						} else {
							$conditions[] = $row;
						}
					}
					unset( $valid_post_types );
				}
				break;

			case 'taxonomy':
				// Sanitize/Validate taxonomy and optional terms.
				if ( ! empty( $data['taxonomy']['taxonomy'] ) ) {
					$conditions = [];
					$taxonomies = array_unique( array_filter( array_map( 'strtolower', $data['taxonomy']['taxonomy'] ) ) );

					// Compare against all known public taxonomies, only need the names.
					$valid_taxes = get_taxonomies(
						array(
							'public'  => true,
							'rewrite' => true,
						),
						'names'
					);
					foreach ( $taxonomies as $row ) {
						if ( ! isset( $valid_taxes[ $row ] ) ) {
							$this->addValidationError( 'conditions', __( 'A valid taxonomy needs to be selected', 'personalizewp' ) );
							$invalid = true;

						} elseif ( empty( $data['taxonomy'][ $row ] ) ) {
							// Check on possible terms in taxonomy.
							// Simply add the taxonomy by itself, no specific terms.
							$conditions[ $row ] = [];
						} else {
							// Sanitise incoming term IDs
							$maybe_term_ids = array_unique( array_filter( array_map( 'intval', $data['taxonomy'][ $row ] ) ) );
							foreach ( $maybe_term_ids as $term_id ) {
								// Ensure the term for the requested taxonomy exists.
								$term = get_term( $term_id, $row );
								if ( is_a( $term, '\WP_Term' ) ) {
									$conditions[ $row ][] = $term->term_id;
								} else {
									$this->addValidationError( 'conditions', __( 'A valid term needs to be selected', 'personalizewp' ) );
									$invalid = true;
								}
							}
						}
					}
					unset( $valid_taxes );
				}
				break;

			case 'product':
				if ( ! empty( $data['product'] ) ) {
					// Sanitize/Validate product/categories.
					$conditions      = [];
					$data['product'] = array_map( 'strtolower', $data['product'] );
					foreach ( $data['product'] as $i => $row ) {
						if ( empty( $row ) ) {
							$this->addValidationError( 'conditions', __( 'A valid product or product category needs to be selected', 'personalizewp' ) );
							$invalid = true;
						}
						if ( ! str_contains( $row, ':' ) ) {
							continue;
						}
						// Split into "[plugin]:[type]:[id]" e.g. "woocommerce:product:245";
						$rowdata = array_map( 'sanitize_text_field', explode( ':', $row, 3 ) );
						if ( 3 !== count( $rowdata ) ) {
							continue;
						}
						$conditions[ $rowdata[0] ][ $rowdata[1] ][] = (int) $rowdata[2];
					}
				}
				break;

			case 'referer':
				if ( ! empty( $data['referer']['url'] ) ) {
					$conditions = [];
					foreach ( $data['referer']['url'] as $i => $row ) {
						$url      = sanitize_text_field( $row );
						$is_regex = ! empty( $data['referer']['regex'][ $i ] );
						if ( empty( $url ) ) {
							$this->addValidationError( 'conditions', __( 'A referring URL needs to be entered', 'personalizewp' ) );
							$invalid = true;
						} else {
							// Mark as either straight URL or raw regex.
							$conditions[ $url ] = $is_regex ? 'regex' : 'url';
						}
					}
				}
				break;

			case 'form':
				// Sanitize/Validate forms
				if ( ! empty( $data['form'] ) ) {
					$conditions = [];
					foreach ( $data['form'] as $i => $row ) {
						$valid_form = false;
						$form_type  = '';
						$form_id    = 0;
						if ( str_contains( $row, ':' ) ) {
							list( $form_type, $form_id ) = explode( ':', $row );
							$form_id                     = (int) $form_id;
							$form_type                   = strtolower( $form_type );
						}

						// Check each supported Form plugin. Each have their own API.
						switch ( $form_type ) {
							case 'gravityforms':
								// Validate that the GF form exists.
								if ( class_exists( '\GFAPI' ) && false !== \GFAPI::get_form( $form_id ) ) {
									$conditions[ $form_id ] = $form_type;
									$valid_form             = true;
								}
								break;
							case 'ninjaforms':
								// Validate that the NF form exists.
								if ( class_exists( '\Ninja_Forms' ) && false !== \Ninja_Forms()->form( $form_id )->get() ) {
									$conditions[ $form_id ] = $form_type;
									$valid_form             = true;
								}
								break;
							case 'wsform':
								// Validate that the WSF form exists.
								if ( class_exists( '\WS_Form' ) && false !== wsf_form_get_label_by_id( $form_id ) ) {
									$conditions[ $form_id ] = $form_type;
									$valid_form             = true;
								}
								break;
						}

						if ( ! $valid_form ) {
							$this->addValidationError( 'conditions', __( 'A valid form needs to be selected', 'personalizewp' ) );
							$invalid = true;
						}
					}
				}
				break;

			case 'query':
				// Sanitize/Validate query key/values
				if ( ! empty( $data['query']['key'] ) ) {
					$conditions = [];
					foreach ( $data['query']['key'] as $i => $row ) {
						$query_key = strtolower( $row );
						$query_val = strtolower( $data['query']['value'][ $i ] );

						// Value is optional, so can be empty.
						if ( empty( $query_key ) ) {
							$this->addValidationError( 'conditions', __( 'A valid query key needs to be entered', 'personalizewp' ) );
							$invalid = true;
						} else {
							$conditions[ $query_key ][] = $query_val;
						}
					}
				}
				break;

			case 'page':
				// Clear out empty values.
				$data['page']['id']  = isset( $data['page']['id'] ) ? array_filter( $data['page']['id'] ) : [];
				$data['page']['url'] = isset( $data['page']['url'] ) ? array_filter( $data['page']['url'] ) : [];
				if ( empty( $data['page']['id'] ) && empty( $data['page']['url'] ) ) {
					$this->addValidationError( 'conditions', __( 'A valid page needs to be selected, or a URL needs entering', 'personalizewp' ) );
					$invalid = true;
				} else {
					// Sanitize/Validate Pages
					$conditions = [];
					// ID and/or URL, we prioritise the ID for actual Posts.
					foreach ( $data['page']['id'] as $id ) {
						$post_entry = get_post( (int) $id );
						if ( ! is_a( $post_entry, '\WP_Post' ) ) {
							$this->addValidationError( 'conditions', __( 'A valid page needs to be selected, or a URL needs entering', 'personalizewp' ) );
							$invalid = true;
						} else {
							$conditions[] = (int) $id;
						}
						unset( $post_entry );
					}
					foreach ( $data['page']['url'] as $i => $url ) {
						$is_regex = ! empty( $data['page']['regex'][ $i ] );
						// Only sanitise non-regex URLs, otherwise regex destroyed.
						if ( ! $is_regex ) {
							$url = sanitize_url( $url );
						}
						if ( empty( $url ) ) {
							$this->addValidationError( 'conditions', __( 'A valid page needs to be selected, or a URL needs entering', 'personalizewp' ) );
							$invalid = true;
						} else {
							// Mark as either straight URL or raw regex.
							$conditions[ $url ] = $is_regex ? 'regex' : 'url';
						}
					}
				}
				break;

		}
		// Encode, but only if required.
		if ( is_array( $conditions ) ) {
			$valid_data['conditions'] = wp_json_encode( $conditions );
		} else {
			$valid_data['conditions'] = $conditions;
		}

		return ! $invalid ? $valid_data : false;
	}

	/**
	 * Filters the clauses used to select Contacts for Segments
	 *
	 * @param string[] $clauses  Associative array of the clauses for the query.
	 *
	 * @return string[]
	 */
	public function filter_contacts_clauses( $clauses ) {
		global $wpdb;

		// Join with segment relationship
		$current_page = isset( $_REQUEST['page'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: verified elsewhere
		$segment_id   = isset( $_REQUEST['id'] ) ? (int) $_REQUEST['id'] : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: used as intval

		if ( $segment_id && $current_page && self::SLUG === $current_page ) {
			$clauses['join']   = "INNER JOIN {$wpdb->prefix}pwp_segments_relationships ON {$wpdb->prefix}pwp_segments_relationships.contact_id = {$wpdb->prefix}pwp_contacts.ID";
			$clauses['where'] .= $wpdb->prepare( "AND {$wpdb->prefix}pwp_segments_relationships.%i = %d", 'segment_id', $segment_id );
		}

		return $clauses;
	}

	/**
	 * Filter the Contacts' actions when displaying within a Segment.
	 *
	 * @param string[] $actions  An array of action links for each contact.
	 *                           Default 'View', 'Delete'.
	 * @param array    $item     Contact record being acted upon.
	 *
	 * @return array Actions
	 */
	public function filter_contacts_actions( $actions, $item ) {
		unset( $actions['delete'] );

		return $actions;
	}

	/**
	 * Add extra inputs to keep the Contacts search within Segments
	 */
	public function add_contacts_search_fields() {

		if ( empty( $_REQUEST[ Admin::URL_ACTION ] ) || empty( $_REQUEST['id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: verified elsewhere
			return;
		}

		// We default to listing all Segments, so add these to ensure displaying single Segment
		?>
		<input type="hidden" name="<?php echo esc_attr( Admin::URL_ACTION ); ?>" value="<?php echo esc_attr( sanitize_text_field( wp_unslash( $_REQUEST[ Admin::URL_ACTION ] ) ) );  // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>" />
		<input type="hidden" name="id" value="<?php echo (int) $_REQUEST['id']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: used as intval ?>" />
		<?php
	}

	/**
	 * Returns array of Segments by status Active/inactive
	 *
	 * @return array
	 */
	public static function count_segments() {
		global $wpdb;

		$segments = wp_cache_get( 'personalizewp_segments', 'counts' );
		if ( false === $segments ) {
			$segments = array(
				'active'   => 0,
				'inactive' => 0,
			);

			$results = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Custom db table
				"SELECT is_active, COUNT(*) AS count FROM {$wpdb->prefix}pwp_segments GROUP BY is_active ",
				ARRAY_A
			);

			foreach ( $results as $result ) {
				if ( empty( $result['is_active'] ) ) {
					$segments['inactive'] += $result['count'];
				} else {
					$segments['active'] += $result['count'];
				}
			}

			wp_cache_set( 'personalizewp_segments', $segments, 'counts' );
		}

		return $segments;
	}

	/**
	 * Returns array of Segment types
	 *
	 * @since 1.1.0 Added lead_score type
	 *
	 * @return array
	 */
	public static function get_segment_types() {

		$types = [
			'lead_score' => __( 'Lead Score', 'personalizewp' ),
			'location'   => __( 'Location', 'personalizewp' ),
			'product'    => __( 'Purchased Product', 'personalizewp' ),
			'referer'    => __( 'Referred From', 'personalizewp' ),
			'form'       => __( 'Submitted Form', 'personalizewp' ),
			'query'      => __( 'Query String', 'personalizewp' ),
			'page'       => __( 'Visited Page', 'personalizewp' ),
			'taxonomy'   => __( 'Taxonomy', 'personalizewp' ),
			'post_type'  => __( 'Post Type', 'personalizewp' ),
		];

		/**
		 * Filters the Segment types
		 *
		 * @param array $types   Segment types
		 */
		$types = apply_filters( 'personalizewp_segment_types', $types );

		// Sort Segment types alphabetically, maintaining the keys.
		asort( $types );

		return $types;
	}

	/**
	 * Converts Segment type into translated string.
	 *
	 * @param string $type Segment Type
	 *
	 * @return string
	 */
	public static function translate_type( $type ) {

		$types = self::get_segment_types();
		if ( array_key_exists( $type, $types ) ) {
			$label = $types[ $type ];
		} else {
			$label = __( 'Unknown', 'personalizewp' );
		}

		/**
		 * Filters the Segment type label
		 *
		 * @param string $label  Processed label
		 * @param string $type   Segment type
		 */
		return apply_filters( 'personalizewp_segment_type_label', $label, $type );
	}

	/**
	 * Converts type string into translated string.
	 *
	 * @since 1.1.0 Added lead_score type

	 * @param mixed  $conditions Segment Conditions
	 * @param string $type       Type of Segment
	 *
	 * @return string
	 */
	public static function translate_conditions( $conditions, $type ) {

		$label = '';
		switch ( $type ) {
			case 'form':
				if ( empty( $conditions ) ) {
					$label = __( 'Submitted any form', 'personalizewp' );
				} else {
					$titles = [];
					$forms  = json_decode( $conditions, true );
					foreach ( $forms as $form => $opts ) {
						$titles[] = Utils::get_form_title( (int) $form, $opts );
					}
					unset( $forms );
					if ( ! empty( $titles ) ) {
						$label = sprintf(
							/* translators: 1: Plural list of forms. */
							_n( 'Submitted \'%s\'', 'Submitted \'%s\'', count( $titles ), 'personalizewp' ),
							implode( "', '", array_filter( $titles ) )
						);
					}
				}
				break;

			case 'lead_score':
				list( 'min' => $min_score, 'max' => $max_score ) = json_decode( $conditions, true );
				if ( 0 !== $min_score && empty( $max_score ) ) {
					// Positive minimum score, no maximum
					if ( 0 < $min_score ) {
						$label = sprintf(
							/* translators: %d: Positive minimum score. */
							__( 'Visitors with lead score > %d', 'personalizewp' ),
							number_format_i18n( $min_score, 0 )
						);
					} else {
						$label = sprintf(
							/* translators: %d: Negative minimum score. */
							__( 'Visitors with lead score < %d', 'personalizewp' ),
							number_format_i18n( $min_score, 0 )
						);
					}
				} elseif ( 0 !== $min_score && 0 !== $max_score ) {
					// Positive minimum/maximum scores
					if ( 0 < $min_score ) {
						$label = sprintf(
							/* translators: %1$d: Positive minimum score; %2$d Maximum score; */
							_x( 'Visitors with lead score between %1$d and %2$d', 'Positive minimum score; Maximum score', 'personalizewp' ),
							number_format_i18n( $min_score, 0 ),
							number_format_i18n( $max_score, 0 )
						);
					} else {
						$label = sprintf(
							/* translators: %1$d: Negative minimum score; %2$d Maximum score; */
							_x( 'Visitors with lead score between %1$d and %2$d', 'Negative minimum score; Maximum score', 'personalizewp' ),
							number_format_i18n( $min_score, 0 ),
							number_format_i18n( $max_score, 0 )
						);
					}
				}
				unset( $min_score, $max_score );
				break;

			case 'taxonomy':
				if ( ! empty( $conditions ) ) {

					$titles     = [];
					$taxonomies = json_decode( $conditions, true );

					foreach ( $taxonomies as $taxonomy => $term_ids ) {
						$tax_obj = get_taxonomy( $taxonomy );
						if ( ! is_a( $tax_obj, '\WP_Taxonomy' ) ) {
							// Custom taxonomy that no longer exists.
							$titles[] = sprintf(
								/* translators: 1: Taxonomy name */
								__( 'Taxonomy: \'%s\'', 'personalizewp' ),
								$taxonomy
							);
							continue;
						}

						$term_ids = array_unique( array_filter( array_map( 'intval', $term_ids ) ) ); // Sanitise stored term IDs.
						if ( empty( $term_ids ) ) {
							$titles[] = sprintf(
								/* translators: 1: Taxonomy name */
								__( 'Taxonomy: \'%s\'', 'personalizewp' ),
								$tax_obj->label
							);
						} else {
							// Include term titles in overall condition description.
							$term_titles = [];
							foreach ( $term_ids as $term_id ) {
								// Validate ID to known term.
								$term = get_term( $term_id, $taxonomy );
								// For existing term and taxonomy.
								if ( is_a( $term, '\WP_Term' ) ) {
									$term_titles[] = esc_html( $term->name );
								} else {
									$term_titles[] = __( 'Unknown', 'personalizewp' );
								}
								unset( $term );
							}
							$titles[] = sprintf(
								/* translators: 1: Taxonomy name, 2: Term names */
								__( 'Taxonomy: \'%1$s\', terms: \'%2$s\'', 'personalizewp' ),
								$tax_obj->label,
								implode( ', ', $term_titles ),
							);
						}
						unset( $taxonomy, $tax_obj, $term_ids, $term_titles );
					}
					if ( ! empty( $titles ) ) {
						$label = sprintf(
							/* translators: 1: List of condition entries. */
							__( 'Visited %s', 'personalizewp' ),
							implode( '; ', $titles )
						);
					}
				}

				break;

			case 'product':
				if ( empty( $conditions ) ) {
					$label = __( 'Purchased Product...', 'personalizewp' );
				} else {
					$titles   = [];
					$products = json_decode( $conditions, true );
					foreach ( $products as $_plugin => $_args ) {
						foreach ( $_args as $_type => $_ids ) {
							foreach ( $_ids as $id ) {
								$titles[] = Utils::get_product_title( $id, $_type, $_plugin );
							}
						}
					}
					unset( $products, $_plugin, $_args, $_type, $_ids );
					if ( ! empty( $titles ) ) {
						$label = sprintf(
							/* translators: 1: List of products/categories. */
							__( 'Purchased %s', 'personalizewp' ),
							implode( ', ', $titles )
						);
					}
				}
				break;

			case 'page':
				if ( empty( $conditions ) ) {
					$label = __( 'Visited any page', 'personalizewp' );
				} else {
					$titles = [];
					$pages  = json_decode( $conditions, true );
					foreach ( $pages as $index => $page ) {
						if ( is_numeric( $page ) ) {
							$titles[] = sprintf(
								/* translators: 1: Page title */
								__( 'Page: \'%s\'', 'personalizewp' ),
								get_the_title( (int) $page )
							);
						} elseif ( is_numeric( $index ) && ! is_numeric( $page ) ) {
							$titles[] = sprintf(
								/* translators: 1: URL */
								__( 'URL: \'%s\'', 'personalizewp' ),
								$page
							);
						} elseif ( ! is_numeric( $index ) && ! is_numeric( $page ) ) {
							$titles[] = sprintf(
								/* translators: 1: URL */
								__( 'URL: \'%s\'', 'personalizewp' ),
								$index
							);
						}
					}
					unset( $pages, $page );
					if ( ! empty( $titles ) ) {
						$label = sprintf(
							/* translators: 1: List of condition entries. */
							__( 'Visited %s', 'personalizewp' ),
							implode( ', ', $titles )
						);
					}
				}

				break;
			case 'location':
				$label     = __( 'Location...', 'personalizewp' );
				$titles    = [];
				$isocodes  = json_decode( $conditions, true );
				$countries = \personalizewp()->get_countries();
				foreach ( $isocodes as $code ) {
					$titles[] = isset( $countries[ $code ] ) ? $countries[ $code ] : '';
				}
				unset( $isocodes, $countries, $code );
				if ( ! empty( $titles ) ) {
					$label = sprintf(
						/* translators: 1: Plural list of countries. */
						__( 'Visitors from \'%s\'', 'personalizewp' ),
						implode( "', '", $titles )
					);
				}
				break;

			case 'query':
				$label = __( 'Query String...', 'personalizewp' );
				if ( ! empty( $conditions ) ) {
					$titles = array_keys( json_decode( $conditions, true ) );
					$label  = sprintf(
						/* translators: 1: List of query keys. */
						__( 'Query String \'%s\'', 'personalizewp' ),
						implode( "', '", $titles )
					);
				}
				break;

			case 'referer':
				$label = __( 'Referred from...', 'personalizewp' );
				if ( ! empty( $conditions ) ) {
					$titles = array_keys( json_decode( $conditions, true ) );
					$label  = sprintf(
						/* translators: 1: List of referrers. */
						__( 'Referred from \'%s\'', 'personalizewp' ),
						implode( "', '", $titles )
					);
				}

				break;

			case 'post_type':
				if ( ! empty( $conditions ) ) {
					$titles     = [];
					$post_types = json_decode( $conditions, true );
					foreach ( $post_types as $post_type ) {
						$post_type_obj = get_post_type_object( $post_type );
						if ( is_a( $post_type_obj, '\WP_Post_Type' ) ) {
							$titles[] = sprintf(
								/* translators: 1: Post Type name */
								__( 'Post Type: \'%s\'', 'personalizewp' ),
								$post_type_obj->label
							);
						} else {
							$titles[] = __( 'Post Type: unknown', 'personalizewp' );
						}
					}
					unset( $post_types, $post_type, $post_type_obj );
					if ( ! empty( $titles ) ) {
						$label = sprintf(
							/* translators: 1: List of condition entries. */
							__( 'Visited %s', 'personalizewp' ),
							implode( ', ', $titles )
						);
					}
				}

				break;
		}

		if ( empty( $label ) ) {
			$label = __( 'Unknown', 'personalizewp' );
		}

		/**
		 * Filters the Segment conditions label
		 *
		 * @param string $label       Processed label
		 * @param string $conditions  Segment conditions
		 * @param string $type        Segment type
		 */
		return apply_filters( 'personalizewp_segment_conditions_label', $label, $conditions, $type );
	}
}
