<?php
/**
 * Teams for WooCommerce Memberships Integration
 *
 * @link https://personalizewp.com/
 * @since 3.4.0
 *
 * @package PersonalizeWP
 * @subpackage PersonalizeWP/Integrations/WooCommerce
 */

namespace PersonalizeWP\Integrations\WooCommerce;

use PersonalizeWP\Admin\Admin;
use PersonalizeWP\Admin\PageActivities;
use PersonalizeWP\Traits\SingletonTrait;
use PersonalizeWP\Admin\PageContacts;
use PersonalizeWP\Model\Contact;
use SkyVerge\WooCommerce\Memberships\Teams\Team;

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

/**
 * PersonalizeWP - Teams for WooCommerce Memberships
 */
final class WooCommerce_Memberships_Teams {

	use SingletonTrait;

	/**
	 * Status tracking
	 *
	 * @var bool
	 */
	private static $is_active = false;

	/** @var array memoization helper */
	private $teams = array();

	/** @var string Selected team when exporting  */
	private $team_id = null;

	/**
	 * Execute WordPress hooks
	 */
	public function setup() {
		// Mark integration as activated
		self::$is_active = true;

		$this->team_id = isset( $_REQUEST['team_id'] ) ? (int) sanitize_text_field( wp_unslash( $_REQUEST['team_id'] ) ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: used as intval

		// Add team column to Activity list table
		add_filter( 'personalizewp_activity_table_columns', [ $this, 'activity_list_add_team_column' ] );
		add_filter( 'personalizewp_activity_table_custom_column', [ $this, 'activity_list_render_team_column' ], 10, 3 );

		// Add teams filtering to Activity list table
		add_action( 'personalizewp_activity_filter_fields', [ $this, 'activity_list_add_filter_by_teams' ] );
		add_filter( 'personalizewp_activity_request_clauses', [ $this, 'filter_activities_by_team' ] );

		// Add visitor profile filtering to Activity list table
		add_action( 'personalizewp_activity_filter_fields', [ $this, 'activity_list_add_filter_by_contact' ], 5 );
		add_filter( 'personalizewp_activity_request_clauses', [ $this, 'filter_activities_by_contact' ], 5 );

		// Add team column to Contacts list table
		add_filter( 'personalizewp_contacts_table_columns', [ $this, 'contacts_list_add_team_column' ] );
		add_filter( 'personalizewp_contacts_table_custom_column', [ $this, 'contacts_list_render_team_column' ], 10, 3 );

		// Add teams filtering within Contacts list table
		add_action( 'personalizewp_contacts_filter_fields', [ $this, 'contacts_list_add_filter_by_teams' ] );
		add_filter( 'personalizewp_contacts_clauses_request', [ $this, 'filter_contacts_by_team' ] );

		// Add team field to contact profile screen
		add_filter( 'personalizewp_contact_profile_fields', [ $this, 'add_team_profile_field' ], 10, 2 );

		// Data export screen
		add_filter( 'personalizewp_export_data_types', [ $this, 'export_add_team_export_type' ] );
		add_filter( 'personalizewp_export_default_export_type', [ $this, 'export_preselect_team_export_type' ] );
		add_action( 'personalizewp_export_after_select_data_types', [ $this, 'export_add_team_dropdown' ] );

		// Data exporting
		add_filter( 'personalizewp_export_params', [ $this, 'add_team_to_export_params' ], 10, 2 );
		add_filter( 'personalizewp_export_request_clauses', [ $this, 'filter_export_query_by_team' ], 10, 3 );
		add_filter( 'personalizewp_export_csv_headers', [ $this, 'export_csv_add_team_header' ] );
		add_filter( 'personalizewp_export_csv_row', [ $this, 'export_csv_add_team_data' ], 10, 2 );

		// Sync team data to Contacts, such as when becoming known.
		add_action( 'personalizewp_added_contact_meta', [ $this, 'sync_membership_team_id_on_contact' ], 10, 4 );
		add_action( 'personalizewp_updated_contact_meta', [ $this, 'sync_membership_team_id_on_contact' ], 10, 4 );
		add_action( 'personalizewp_deleted_contact_meta', [ $this, 'delete_membership_team_id_from_contact' ], 10, 4 );

		// Auto-population hooks for team data synchronization
		add_action( 'wc_memberships_for_teams_add_team_member', [ $this, 'add_contacts_to_team' ], 10, 3 );
		add_action( 'wc_memberships_for_teams_after_remove_team_member', [ $this, 'remove_contacts_from_team' ], 10, 2 );
	}

	/**
	 * Check if the integration is currently active.
	 *
	 * @since 3.4.0
	 * @return bool
	 */
	public static function is_active(): bool {
		return self::$is_active;
	}

	/**
	 * Check if the current admin screen is the profile listings screen or not.
	 *
	 * @since 3.4.0
	 * @return bool
	 */
	public function is_profile_listing_screen(): bool {
		$screen = get_current_screen();
		return is_object( $screen ) && str_ends_with( $screen->id, PageContacts::SLUG );
	}

	/**
	 * Check if the current admin screen is the recent events listing screen or not.
	 *
	 * @since 3.4.0
	 * @return bool
	 */
	public function is_recent_events_screen(): bool {
		$screen = get_current_screen();
		return is_object( $screen ) && str_ends_with( $screen->id, PageActivities::SLUG );
	}

	/**
	 * Check if the current admin screen is a Team profile screen or not.
	 *
	 * @since 3.4.0
	 * @return bool
	 */
	public function is_team_profile_screen(): bool {
		$screen          = get_current_screen();
		$is_profile_page = is_object( $screen ) && str_ends_with( $screen->id, PageContacts::SLUG );
		$action          = isset( $_REQUEST[ Admin::URL_ACTION ] ) ? sanitize_text_field( wp_unslash( $_REQUEST[ Admin::URL_ACTION ] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: nonce verification not required
		$is_team_profile = 'view-team' === $action;

		return ( $is_profile_page && $is_team_profile );
	}

	/**
	 * Get team by ID via the Teams for Memberships API.
	 *
	 * @uses wc_memberships_for_teams_get_team()
	 *
	 * @since 3.4.0
	 *
	 * @param int $team_id Team ID.
	 * @return array|null Team data or null if not found.
	 */
	public function get_team_by_id( int $team_id ) {

		if ( empty( $team_id ) ) {
			return null;
		}

		$teams = $this->get_membership_teams();
		if ( empty( $teams[ $team_id ] ) ) {
			return null;
		}

		return $teams[ $team_id ];
	}

	/**
	 * Get all Memberships teams for use in UI dropdowns etc, also populates team cache.
	 *
	 * @since 3.4.0
	 *
	 * @return array Array of teams data, indexed by team ID.
	 */
	public function get_membership_teams(): array {

		if ( ! function_exists( 'wc_memberships_for_teams' ) ) {
			return array();
		}

		// Check local cache.
		if ( ! empty( $this->teams ) ) {
			return $this->teams;
		}

		// Start afresh.
		$this->teams   = array();
		$teams_handler = wc_memberships_for_teams()->get_teams_handler_instance();
		$_teams_query  = new \WP_Query(
			array(
				'post_type'        => 'wc_memberships_team',
				'status'           => 'any',
				'fields'           => 'ids',
				'posts_per_page'   => -1,
				'nopaging'         => true,
				'suppress_filters' => false,
			)
		);
		foreach ( $_teams_query->posts as $team_id ) {
			$team = $teams_handler->get_team( $team_id );
			if ( ! $team ) {
				continue;
			}
			$this->teams[ $team_id ] = array(
				'ID'         => $team_id,
				'team_name'  => $team->get_name(),
				'team_email' => '', // Not currently supported
			);
		}
		return $this->teams;
	}

	/**
	 * Get team members for a specific team. Validates the team exists.
	 *
	 * @since 3.4.0
	 *
	 * @param int $team_id Team ID.
	 * @return array Array of contact IDs that are team members.
	 */
	public function get_team_members( int $team_id ): array {

		if ( empty( $team_id ) ) {
			return array();
		}

		// Validate team exists.
		$team = $this->get_team_by_id( $team_id );
		if ( empty( $team ) ) {
			return array();
		}

		// Directly use cached data.
		if ( empty( $this->teams[ $team_id ] ) || empty( $this->teams[ $team_id ]['member_ids'] ) ) {

			// Populates member IDs
			if ( ! isset( $this->teams[ $team_id ]['member_ids'] ) ) {
				$this->teams[ $team_id ]['member_ids'] = Contact::get_contacts_by_meta( 'wc_memberships_team_id', $team_id );
			} else {
				$this->teams[ $team_id ]['member_ids'] = array();
			}
		}

		return $this->teams[ $team_id ]['member_ids'];
	}

	/**
	 * Add team column to activity list table.
	 *
	 * @since 3.4.0
	 * @param array $columns The existing columns.
	 * @return array Modified columns array.
	 */
	public function activity_list_add_team_column( array $columns ): array {

		// Ensure only on main Activity screen.
		if ( ! $this->is_recent_events_screen() ) {
			return $columns;
		}

		// Insert team column before activity_type column
		$new_columns = [];
		foreach ( $columns as $key => $label ) {
			if ( 'activity_type' === $key ) {
				$new_columns['team'] = __( 'Team', 'personalizewp' );
			}
			$new_columns[ $key ] = $label;
		}
		return $new_columns;
	}

	/**
	 * Render team column content in activity list table.
	 *
	 * @since 3.4.0
	 *
	 * @param string $col         The column content.
	 * @param string $column_name The column name.
	 * @param array  $item        The activity entry.
	 * @return string The column content.
	 */
	public function activity_list_render_team_column( string $col, string $column_name, array $item ): string {

		if ( 'team' !== $column_name ) {
			return $col;
		}

		$contact = Contact::find( $item['contact_id'] );
		if ( ! $contact ) {
			return '';
		}

		$team_id = (int) $contact->get_metadata( 'wc_memberships_team_id', true );
		$team    = $this->get_team_by_id( $team_id );

		// Return clickable link to team profile screen.
		return $this->format_team_link( $team );
	}

	/**
	 * Add filtering by Teams to the main activity listing table.
	 *
	 * @used-by 'personalizewp_activity_filter_fields' action
	 *
	 * @since 3.4.0
	 */
	public function activity_list_add_filter_by_teams(): void {

		// Ensure only on main Activity screen.
		if ( ! $this->is_recent_events_screen() ) {
			// Nothing to do, remove from being considered.
			remove_action( 'personalizewp_activity_filter_fields', [ $this, __FUNCTION__ ] );
			return;
		}

		/**
		 * Filter the WC Membership Teams available for filtering.
		 *
		 * @since 3.4.0
		 * @param array $teams Array of team data.
		 */
		$teams = apply_filters( 'personalizewp_activity_filter_membership_teams', $this->get_membership_teams() );
		if ( empty( $teams ) ) {
			return;
		}
		// Limit to just ID and name.
		$teams = wp_list_pluck( $teams, 'team_name', 'ID' );
		// Order alphabetically.
		asort( $teams );

		$current_team_id = ! empty( $this->team_id ) ? $this->team_id : 0;
		?>
		<label for="team-filter-contacts" class="screen-reader-text"><?php esc_html_e( 'Filter by team', 'personalizewp' ); ?></label>
		<select name="team_id" id="team-filter-contacts">
			<option value="" <?php selected( $current_team_id, 0 ); ?>><?php esc_html_e( 'All Teams', 'personalizewp' ); ?></option>
			<?php foreach ( $teams as $team_id => $team_name ) : ?>
				<option value="<?php echo esc_attr( $team_id ); ?>" <?php selected( $current_team_id, $team_id ); ?>>
					<?php echo esc_html( $team_name ); ?>
				</option>
			<?php endforeach; ?>
		</select>
		<?php
	}

	/**
	 * Filter activities list by team using clauses filter.
	 *
	 * @since 3.4.0
	 *
	 * @param array $clauses The query clauses.
	 * @return array Modified clauses.
	 */
	public function filter_activities_by_team( array $clauses ): array {
		global $wpdb;

		// Ensure only used when a team has been selected.
		if ( empty( $this->team_id ) ) {
			return $clauses;
		}

		$team_member_ids = $this->get_team_members( $this->team_id );

		if ( ! empty( $team_member_ids ) ) {
			$ids_placeholders  = implode( ', ', array_fill( 0, count( $team_member_ids ), '%d' ) );
			$clauses['where'] .= $wpdb->prepare( " AND contact_id IN ( {$ids_placeholders} )", $team_member_ids ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: using placeholders
		} else {
			// Force an invalid search to return no results
			$clauses['where'] .= ' AND 1=0';
		}

		return $clauses;
	}

	/**
	 * Add filtering by Visitor Profiles to a Teams' activity list table.
	 *
	 * @used-by 'personalizewp_activity_filter_fields' action
	 *
	 * @since 3.4.0
	 */
	public function activity_list_add_filter_by_contact() {

		// Ensure only on Team Activity screen.
		if ( ! $this->is_team_profile_screen() || empty( $this->team_id ) ) {
			// Nothing to do, remove from being considered.
			remove_action( 'personalizewp_activity_filter_fields', [ $this, __FUNCTION__ ], 5 );
			return;
		}

		$team_member_ids   = $this->get_team_members( $this->team_id );
		$filter_contact_id = isset( $_REQUEST['contact_id'] ) ? absint( wp_unslash( $_REQUEST['contact_id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		?>
		<label for="contact_filter" class="screen-reader-text"><?php esc_html_e( 'Filter by visitor', 'personalizewp' ); ?></label>
		<select name="contact_id" id="contact_filter">
			<option value="" <?php selected( $filter_contact_id, 0 ); ?>><?php esc_html_e( 'All Visitors', 'personalizewp' ); ?></option>
			<?php
			foreach ( $team_member_ids as $contact_id ) :
				$contact = Contact::find( $contact_id );
				$name    = trim( $contact->first_name . ' ' . $contact->last_name );
				if ( '' === $name ) {
					// Skip unknown members.
					continue;
				}
				?>
				<option value="<?php echo esc_attr( $contact->ID ); ?>" <?php selected( $filter_contact_id, $contact->ID ); ?>>
					<?php echo esc_html( $name ); ?>
				</option>
				<?php
			endforeach;
			?>
		</select>
		<?php
	}

	/**
	 * Filter activities list by Visitor Profile using clauses filter.
	 *
	 * @since 3.4.0
	 *
	 * @param array $clauses The query clauses.
	 * @return array Modified clauses.
	 */
	public function filter_activities_by_contact( $clauses ): array {
		global $wpdb;

		// Ensure only on Team Activity screen
		$filter_contact_id = isset( $_REQUEST['contact_id'] ) ? absint( wp_unslash( $_REQUEST['contact_id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended

		// Ensure only used on Team Activity screen.
		if ( ! $this->is_team_profile_screen() || empty( $filter_contact_id ) ) {
			// Nothing to.
			return $clauses;
		}
		// Restricting to a specific Visitor means not having to restrict to Teams' member IDs. So remove.
		remove_filter( 'personalizewp_activity_request_clauses', [ $this, 'filter_activities_by_team' ] );

		$clauses['where'] .= $wpdb->prepare( ' AND contact_id = %d', $filter_contact_id );

		return $clauses;
	}

	/**
	 * Add team column to contacts list table.
	 *
	 * @since 3.4.0
	 *
	 * @param array $columns The existing columns.
	 * @return array Modified columns array.
	 */
	public function contacts_list_add_team_column( array $columns ): array {

		// Ensure only on main Profile listings screen.
		if ( ! $this->is_profile_listing_screen() ) {
			return $columns;
		}

		// Insert team column before email column
		$new_columns = [];
		foreach ( $columns as $key => $label ) {
			if ( 'email' === $key ) {
				$new_columns['team'] = __( 'Team', 'personalizewp' );
			}
			$new_columns[ $key ] = $label;
		}
		return $new_columns;
	}

	/**
	 * Render team column content in contacts list table.
	 *
	 * @since 3.4.0
	 *
	 * @param string $col         The column content.
	 * @param string $column_name The column name.
	 * @param array  $item        The contact entry.
	 * @return string The column content.
	 */
	public function contacts_list_render_team_column( string $col, string $column_name, array $item ): string {

		if ( 'team' !== $column_name ) {
			return $col;
		}

		$contact = Contact::find( $item['ID'] );
		if ( ! $contact ) {
			return '';
		}

		$team_id = (int) $contact->get_metadata( 'wc_memberships_team_id', true );
		if ( empty( $team_id ) ) {
			return '';
		}

		$teams = $this->get_membership_teams();
		if ( ! isset( $teams[ $team_id ] ) ) {
			return '';
		}

		// Return clickable link to team profile screen.
		return $this->format_team_link( $teams[ $team_id ] );
	}

	/**
	 * Add filtering by Teams to contacts list table.
	 *
	 * @used-by 'personalizewp_contacts_filter_fields' action
	 *
	 * @since 3.4.0
	 */
	public function contacts_list_add_filter_by_teams(): void {

		// Ensure only on main Profile listings screen.
		if ( ! $this->is_profile_listing_screen() ) {
			// Nothing to do, remove from being considered.
			remove_action( 'personalizewp_contacts_filter_fields', [ $this, __FUNCTION__ ] );
			return;
		}
		/**
		 * Filter the WC Membership Teams available for filtering.
		 *
		 * @since 3.4.0
		 * @param array $teams Array of team data.
		 */
		$teams = apply_filters( 'personalizewp_contacts_filter_membership_teams', $this->get_membership_teams() );
		if ( empty( $teams ) ) {
			return;
		}
		// Limit to just ID and name.
		$teams = wp_list_pluck( $teams, 'team_name', 'ID' );
		// Order alphabetically.
		asort( $teams );

		$current_team_id = ! empty( $this->team_id ) ? $this->team_id : 0;
		?>
		<label for="team-filter-contacts" class="screen-reader-text"><?php esc_html_e( 'Filter by team', 'personalizewp' ); ?></label>
		<select name="team_id" id="team-filter-contacts">
			<option value="" <?php selected( $current_team_id, 0 ); ?>><?php esc_html_e( 'All Teams', 'personalizewp' ); ?></option>
			<?php foreach ( $teams as $team_id => $team_name ) : ?>
				<option value="<?php echo esc_attr( $team_id ); ?>" <?php selected( $current_team_id, $team_id ); ?>>
					<?php echo esc_html( $team_name ); ?>
				</option>
			<?php endforeach; ?>
		</select>
		<?php
	}

	/**
	 * Filter contacts list by team using clauses filter.
	 *
	 * @since 3.4.0
	 * @param array $clauses The query clauses.
	 * @return array Modified clauses.
	 */
	public function filter_contacts_by_team( array $clauses ): array {
		global $wpdb;

		// Ensure only used on main Profiles listing screen.
		if ( ! $this->is_profile_listing_screen() || empty( $this->team_id ) ) {
			return $clauses;
		}

		$team_member_ids = $this->get_team_members( $this->team_id );

		if ( ! empty( $team_member_ids ) ) {
			$ids_placeholders  = implode( ', ', array_fill( 0, count( $team_member_ids ), '%d' ) );
			$clauses['where'] .= $wpdb->prepare( " AND ID IN ( {$ids_placeholders} )", $team_member_ids ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: using placeholders
		} else {
			// Force an invalid search to return no results
			$clauses['where'] .= ' AND 1=0';
		}

		return $clauses;
	}

	/**
	 * Add team field to contact profile display screen.
	 *
	 * @since 3.4.0
	 *
	 * @param array   $profile_fields The profile fields.
	 * @param Contact $contact The contact object.

	 * @return array Modified profile fields.
	 */
	public function add_team_profile_field( array $profile_fields, $contact ): array {

		$team_id = (int) $contact->get_metadata( 'wc_memberships_team_id', true );
		$team    = $this->get_team_by_id( $team_id );

		// Insert team column before phone number field.
		$reordered_fields = [];
		foreach ( $profile_fields as $key => $label ) {
			if ( 'phone_number' === $key ) {
				$reordered_fields['team'] = array(
					'label' => __( 'Team', 'personalizewp' ),
					'value' => ( $team ) ? $this->format_team_link( $team ) : '-',
				);
			}
			$reordered_fields[ $key ] = $label;
		}
		return $reordered_fields;
	}

	/**
	 * Add team option to export options on export screen.
	 *
	 * @since 3.4.0
	 *
	 * @param array $export_types The existing export types.
	 * @return array Modified columns array.
	 */
	public function export_add_team_export_type( array $export_types ): array {

		// Insert team option after all_visitors option
		$new_export_types = [];
		foreach ( $export_types as $key => $label ) {
			$new_export_types[ $key ] = $label;
			if ( 'all_visitors' === $key ) {
				$new_export_types['team'] = __( 'Team', 'personalizewp' );
			}
		}
		return $new_export_types;
	}

	/**
	 * Filter the pre-selected default export type.
	 *
	 * @since 3.4.0
	 *
	 * @param string $export_default The default export type.
	 * @return string Modified default.
	 */
	public function export_preselect_team_export_type( string $export_default ): string {

		if ( ! empty( $this->team_id ) ) {
			$export_default = 'team';
		}

		return $export_default;
	}

	/**
	 * Ouput team export dropdown to data export screen.
	 *
	 * @since 3.4.0
	 */
	public function export_add_team_dropdown(): void {

		/**
		 * Filter the WC Membership Teams available for exporting.
		 *
		 * @since 3.4.0
		 * @param array $teams Array of team data.
		 */
		$teams = apply_filters( 'personalizewp_export_filter_membership_teams', $this->get_membership_teams() );
		if ( empty( $teams ) ) {
			return;
		}
		// Limit to just ID and name.
		$teams = wp_list_pluck( $teams, 'team_name', 'ID' );
		// Order alphabetically.
		asort( $teams );

		$selected_team = ! empty( $this->team_id ) ? $this->team_id : 0;
		?>
		<div class="flow" data-selection-condition="team" <?php echo $selected_team ? '' : 'hidden'; ?>>
			<label for="team-select"><?php esc_html_e( 'Select Team', 'personalizewp' ); ?></label>
			<select name="team_id" id="team-select" class="form-control">
				<?php
				foreach ( $teams as $team_id => $team_name ) :
					?>
					<option value="<?php echo esc_attr( $team_id ); ?>" <?php selected( $selected_team, $team_id ); ?>><?php echo esc_html( $team_name ); ?></option>
					<?php
				endforeach;
				?>
			</select>
		</div>
		<?php
	}

	/**
	 * Add team name to CSV headers.
	 *
	 * @since 3.4.0
	 *
	 * @param array $headers CSV headers.
	 *
	 * @return array Modified headers.
	 */
	public function export_csv_add_team_header( array $headers ): array {

		// Insert Team Name as third col as cannot use actual col text due to i18n.
		// Expected output CSV structure: First Name, Last Name, Team Name, Event Type, Event Value, Event Date
		$new_heads = array();
		foreach ( $headers as $index => $header ) {
			if ( 2 === $index ) {
				$new_heads[] = __( 'Team Name', 'personalizewp' );
			}
			$new_heads[] = $header;
		}

		return $new_heads;
	}

	/**
	 * Add team name to CSV row data.
	 *
	 * @since 3.4.0
	 *
	 * @param array $csv_row CSV row data.
	 * @param array $item    Activity entry database row.
	 *
	 * @return array Modified CSV row.
	 */
	public function export_csv_add_team_data( array $csv_row, array $item ): array {

		$team_name = '';
		$contact   = Contact::find( $item['contact_id'] );
		if ( $contact ) {
			$team_id = $contact->get_metadata( 'wc_memberships_team_id', true );
			if ( ! empty( $team_id ) ) {
				$team      = $this->get_team_by_id( $team_id );
				$team_name = ! empty( $team ) ? $team['team_name'] : '';
			}
		}

		// Insert team name as third col after last name
		// Expected output CSV structure: First Name, Last Name, Team Name, Event Type, Event Value, Event Date
		$new_row = array();
		foreach ( $csv_row as $index => $value ) {
			if ( 2 === $index ) {
				$new_row[] = $team_name;
			}
			$new_row[] = $value;
		}

		return $new_row;
	}

	/**
	 * Format Team name as clickable link to their Team screen.
	 *
	 * @since 3.4.0
	 *
	 * @param array $team Team data.
	 *
	 * @return string Formatted link HTML or empty if invalid team data.
	 */
	public function format_team_link( $team ): string {

		if ( empty( $team ) ) {
			return '';
		}

		$team_url = admin_url( PageContacts::$view_team_url . $team['ID'] );
		return sprintf(
			'<a href="%s" aria-label="%s" rel="bookmark">%s</a>',
			esc_url( $team_url ),
			/* translators: %s: Team name. */
			esc_attr( sprintf( _x( 'View team &#8220;%s&#8221;', 'Team name', 'personalizewp' ), $team['team_name'] ) ),
			esc_html( $team['team_name'] )
		);
	}

	/**
	 * Sanitises an incoming Team ID as an export param.
	 *
	 * @since 3.4.0
	 *
	 * @param array[] $params Default export params
	 * @param array[] $data   Raw form POST data
	 *
	 * @return array[]
	 */
	public function add_team_to_export_params( $params, $data ) {

		$params['team_id'] = isset( $data['team_id'] ) ? (int) sanitize_text_field( $data['team_id'] ) : 0;

		return $params;
	}

	/**
	 * Filter export query to restrict to team members.
	 *
	 * @since 3.4.0
	 *
	 * @param string[] $clauses {
	 *     Associative array of the clauses for the query.
	 *
	 *     @type string $fields  The SELECT clause of the query.
	 *     @type string $join    The JOIN clause of the query.
	 *     @type string $where   The WHERE clause of the query.
	 *     @type string $orderby The ORDER BY clause of the query.
	 *     @type string $limits  The LIMIT clause of the query.
	 * }
	 * @param array    $export_params  Export parameters
	 * @param int      $request_id     Export request ID
	 * @return array Modified query.
	 */
	public function filter_export_query_by_team( array $clauses, array $export_params, int $request_id ): array {
		global $wpdb;

		// Filtering by Team (WordPress Post ID, which will map to many Contact IDs).
		if ( ! empty( $export_params['export_type'] ) && 'team' === $export_params['export_type'] && ! empty( $export_params['team_id'] ) ) {
			$has_valid_team = false;

			// Get the team members, validating the team.
			$team_member_ids = $this->get_team_members( (int) $export_params['team_id'] );
			if ( ! empty( $team_member_ids ) ) {
				$has_valid_team = true;

				// Audit the Contact IDs exported.
				add_post_meta( $request_id, '_export_contact_ids', $team_member_ids, true );

				$placeholders      = implode( ', ', array_fill( 0, count( $team_member_ids ), '%d' ) );
				$clauses['where'] .= $wpdb->prepare( " AND a.contact_id IN ( {$placeholders} )", $team_member_ids ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: using placeholders
			}

			if ( ! $has_valid_team ) {
				// If no valid contacts found for this team (or not valid team), force return no results
				$clauses['where'] .= ' AND 1=0 ';
			}
		}

		return $clauses;
	}

	/**
	 * Return the Membership team the user is a member of.
	 *
	 * Note: doesn't return any team if the user is the team "owner", only if they are a manager or member.
	 *
	 * @since 3.4.0
	 *
	 * @param int $user_id ID of WordPress User
	 * @return int ID of Membership Team, or null if none found or error
	 */
	public function get_membership_team_id_by_user_id( $user_id ) {

		if ( ! function_exists( 'wc_memberships_for_teams_get_teams' ) ) {
			return null;
		}

		$team_id = null;

		// Note: Cannot use 'owner' within role restriction as it overrides the query, excluding the other roles.
		$teams = wc_memberships_for_teams_get_teams( $user_id, array( 'role' => array( 'manager', 'member' ) ), null, true );
		if ( is_array( $teams ) ) {
			$team = array_shift( $teams ); // There should only be 1 result.
			if ( $team instanceof Team ) {
				$team_id = (int) $team->get_id();
			}
		}

		return $team_id;
	}

	/**
	 * Synchronise a users Memberships Team whenever their WP user ID updates.
	 *
	 * @since 3.4.0
	 *
	 * @param int     $meta_id     ID of updated metadata entry.
	 * @param Contact $contact     Contact object.
	 * @param string  $meta_key    Metadata key.
	 * @param mixed   $_meta_value Metadata value.
	 */
	public function sync_membership_team_id_on_contact( $meta_id, $contact, $meta_key, $_meta_value ): void {
		if ( 'wp_user_id' !== $meta_key || ! function_exists( 'wc_memberships_for_teams_get_teams' ) ) {
			return;
		}

		$user_id = (int) $_meta_value;
		if ( ! $user_id ) {
			return;
		}

		$team_id = $this->get_membership_team_id_by_user_id( $user_id );
		if ( empty( $team_id ) ) {
			return;
		}

		// Add/update new meta data
		$contact->update_metadata( 'wc_memberships_team_id', $team_id );
	}

	/**
	 * Deletes a users Memberships Team whenever their WP user ID is deleted.
	 *
	 * @since 3.4.0
	 *
	 * @param int     $meta_id     ID of updated metadata entry.
	 * @param Contact $contact     Contact object.
	 * @param string  $meta_key    Metadata key.
	 * @param mixed   $_meta_value Metadata value.
	 */
	public function delete_membership_team_id_from_contact( $meta_id, $contact, $meta_key, $_meta_value ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
		if ( 'wp_user_id' !== $meta_key ) {
			return;
		}

		// Delete team meta data
		$contact->delete_metadata( 'wc_memberships_team_id' );
	}

	/**
	 * Adds Contacts associated with a WP User ID to a Membership Team.
	 *
	 * @uses 'wc_memberships_for_teams_add_team_member'
	 *
	 * @since 3.4.0
	 *
	 * @param \SkyVerge\WooCommerce\Memberships\Teams\Team_Member $member the team member instance
	 * @param \SkyVerge\WooCommerce\Memberships\Teams\Team        $team the team instance
	 * @param \WC_Memberships_User_Membership                     $user_membership the related user membership instance
	 */
	public function add_contacts_to_team( $member, $team, $user_membership ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed

		$user_id = $member->get_id();
		$team_id = $team->get_id();

		// Many, many Contacts could be assigned to the same WP User ID.
		$contacts = Contact::get_contacts_by_meta( 'wp_user_id', $user_id );

		if ( ! is_array( $contacts ) || empty( $contacts ) ) {
			return;
		}

		foreach ( $contacts as $contact_id ) {
			$contact = Contact::find( $contact_id );
			// Add/update new meta data
			$contact->update_metadata( 'wc_memberships_team_id', $team_id );
		}
	}

	/**
	 * Removes Contacts associated with a WP User ID from a Membership Team.
	 *
	 * @uses 'wc_memberships_for_teams_after_remove_team_member'
	 * @since 3.4.0
	 *
	 * @param int                                          $user_id the id of the user (team member) that was removed
	 * @param \SkyVerge\WooCommerce\Memberships\Teams\Team $team Team instance
	 */
	public function remove_contacts_from_team( $user_id, $team ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed

		// Many, many Contacts could be associated with the same WP User ID.
		$contacts = Contact::get_contacts_by_meta( 'wp_user_id', $user_id );

		if ( ! is_array( $contacts ) || empty( $contacts ) ) {
			return;
		}

		foreach ( $contacts as $contact_id ) {
			$contact = Contact::find( $contact_id );
			// Remove team association.
			$contact->delete_metadata( 'wc_memberships_team_id' );
		}
	}
}
