<?php
/**
 * Donors List Table
 *
 * @package AmbassadorHub\Donors\Admin
 */

namespace AmbassadorHub\Donors\Admin;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use AmbassadorHub\Donors\DonorsModule;

if ( ! class_exists( 'WP_List_Table' ) ) {
	require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}

/**
 * List table for the Donors admin screen.
 */
final class Donors_List_Table extends \WP_List_Table {

	/** Nonce action/field for the search form — see Menu.php's wp_nonce_field() call. */
	private const NONCE_ACTION = 'ambh_donors_search';
	private const NONCE_FIELD  = 'ambh_donors_nonce';

	/**
	 * Base URL including persistent query args (page slug).
	 *
	 * @var string
	 */
	private string $base_url;

	/**
	 * Current search term.
	 *
	 * @var string
	 */
	private string $search_term;

	/**
	 * Active view tab: 'all' or 'anonymized'.
	 *
	 * @var string
	 */
	private string $current_tab;

	/** @var int */
	private int $count_all = 0;

	/** @var int */
	private int $count_identified = 0;

	/** @var int */
	private int $count_anonymized = 0;

	/**
	 * Constructor.
	 *
	 * @param string $base_url Base URL for pagination and action links.
	 */
	public function __construct( string $base_url ) {
		parent::__construct(
			array(
				'singular' => 'donor',
				'plural'   => 'donors',
				'ajax'     => false,
			)
		);

		$this->base_url = $base_url;

		// Search only narrows this already-capability-gated screen's display
		// (no state is written), so an invalid/missing nonce just fails open
		// to an unfiltered view rather than wp_die()-ing.
		$nonce_valid = isset( $_GET[ self::NONCE_FIELD ] )
			&& wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET[ self::NONCE_FIELD ] ) ), self::NONCE_ACTION );

		$this->search_term = ( $nonce_valid && isset( $_REQUEST['s'] ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) : '';
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- plain view-tab navigation link, not a filter/action input.
		$raw_tab           = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'all';
		$this->current_tab = in_array( $raw_tab, array( 'identified', 'anonymized' ), true ) ? $raw_tab : 'all';
	}

	/**
	 * Define the columns.
	 *
	 * @return array<string,string>
	 */
	public function get_columns(): array {
		return array(
			'cb'         => '<input type="checkbox">',
			'name'       => __( 'Name', 'ambassadorhub' ),
			'email'      => __( 'Email', 'ambassadorhub' ),
			'location'   => __( 'Location', 'ambassadorhub' ),
			'donations'  => __( 'Donations', 'ambassadorhub' ),
			'registered' => __( 'Registered', 'ambassadorhub' ),
		);
	}

	/**
	 * Define sortable columns.
	 *
	 * @return array<string,array>
	 */
	protected function get_sortable_columns(): array {
		return array(
			'name'       => array( 'name', false ),
			'email'      => array( 'email', false ),
			'donations'  => array( 'donation_count', false ),
			'registered' => array( 'created_at', true ),
		);
	}

	/**
	 * Primary column name (determines where row actions are rendered).
	 *
	 * @return string
	 */
	protected function get_primary_column_name(): string {
		return 'name';
	}

	/**
	 * Bulk actions available for the table.
	 *
	 * @return array<string,string>
	 */
	protected function get_bulk_actions(): array {
		return array(
			'anonymise' => __( 'Anonymise', 'ambassadorhub' ),
		);
	}

	/**
	 * View links rendered above the table (All / Anonymized tabs).
	 *
	 * @return array<string,string>
	 */
	protected function get_views(): array {
		$labels = array(
			'all'        => __( 'All', 'ambassadorhub' ),
			'identified' => __( 'Identified', 'ambassadorhub' ),
			'anonymized' => __( 'Anonymized', 'ambassadorhub' ),
		);

		$counts = array(
			'all'        => $this->count_all,
			'identified' => $this->count_identified,
			'anonymized' => $this->count_anonymized,
		);

		$urls = array(
			'all'        => remove_query_arg( 'tab', $this->base_url ),
			'identified' => add_query_arg( 'tab', 'identified', $this->base_url ),
			'anonymized' => add_query_arg( 'tab', 'anonymized', $this->base_url ),
		);

		$views = array();
		foreach ( $labels as $slug => $label ) {
			$class          = $slug === $this->current_tab ? ' class="current"' : '';
			$views[ $slug ] = '<a href="' . esc_url( $urls[ $slug ] ) . '"' . $class . '>'
				. esc_html( $label )
				. ' <span class="count">(' . absint( $counts[ $slug ] ) . ')</span></a>';
		}

		return $views;
	}

	/**
	 * Handle row and bulk anonymise actions.
	 *
	 * Must be called before prepare_items() and before any HTML output,
	 * as it redirects and exits on success.
	 *
	 * @return void
	 */
	public function process_bulk_action(): void {
		if ( 'anonymise' !== $this->current_action() ) {
			return;
		}

		if ( ! current_user_can( 'ambh_manage_hub' ) ) {
			wp_die( esc_html__( 'Insufficient permissions', 'ambassadorhub' ) );
		}

		check_admin_referer( 'bulk-donors' );

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

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

		$anonymised = 0;
		foreach ( $ids as $id ) {
			if ( DonorsModule::anonymize( $id ) ) {
				++$anonymised;
			}
		}

		wp_safe_redirect( add_query_arg( 'anonymised', $anonymised, $this->base_url ) );
		exit;
	}

	/**
	 * Fetch and paginate donors.
	 *
	 * @return void
	 */
	public function prepare_items(): void {
		$per_page        = $this->get_items_per_page( 'ambh_donors_per_page', 20 );
		$allowed_orderby = array( 'name', 'email', 'created_at', 'donation_count' );

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$raw_orderby = isset( $_GET['orderby'] ) ? sanitize_text_field( wp_unslash( $_GET['orderby'] ) ) : 'created_at';
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$raw_order = isset( $_GET['order'] ) ? strtoupper( sanitize_text_field( wp_unslash( $_GET['order'] ) ) ) : 'DESC';

		$orderby      = in_array( $raw_orderby, $allowed_orderby, true ) ? $raw_orderby : 'created_at';
		$order        = 'ASC' === $raw_order ? 'ASC' : 'DESC';
		$current_page = $this->get_pagenum();
		$offset       = ( $current_page - 1 ) * $per_page;

		// Populate tab counts in one aggregated query (search-scoped, not tab-scoped — matching WP convention).
		$tab_counts             = DonorsModule::count_tabs( $this->search_term );
		$this->count_all        = $tab_counts['all'];
		$this->count_identified = $tab_counts['identified'];
		$this->count_anonymized = $tab_counts['anonymized'];

		$is_anonymous_filter = match ( $this->current_tab ) {
			'identified' => false,
			'anonymized' => true,
			default      => null,
		};
		$total_items = match ( $this->current_tab ) {
			'identified' => $this->count_identified,
			'anonymized' => $this->count_anonymized,
			default      => $this->count_all,
		};

		$this->items = DonorsModule::get_all(
			array(
				'search'       => $this->search_term,
				'limit'        => $per_page,
				'offset'       => $offset,
				'orderby'      => $orderby,
				'order'        => $order,
				'is_anonymous' => $is_anonymous_filter,
			)
		);

		$this->set_pagination_args(
			array(
				'total_items' => $total_items,
				'per_page'    => $per_page,
				'total_pages' => (int) ceil( $total_items / $per_page ),
			)
		);

		$this->_column_headers = array(
			$this->get_columns(),
			$this->get_hidden_columns(),
			$this->get_sortable_columns(),
			$this->get_primary_column_name(),
		);
	}

	/**
	 * Return the currently hidden columns for this screen.
	 *
	 * @return string[]
	 */
	public function get_hidden_columns(): array {
		$screen = get_current_screen();
		if ( ! $screen ) {
			return array();
		}
		$hidden = get_user_meta( get_current_user_id(), 'manage' . $screen->id . 'columnshidden', true );
		return is_array( $hidden ) ? $hidden : array();
	}

	/**
	 * Checkbox column for bulk actions.
	 *
	 * @param object $item Donor row.
	 * @return string
	 */
	public function column_cb( $item ): string {
		return '<input type="checkbox" name="donor[]" value="' . absint( $item->id ) . '">';
	}

	/**
	 * Name column (primary) with Anonymise row action.
	 *
	 * @param object $item Donor row.
	 * @return string
	 */
	public function column_name( $item ): string {
		$anonymise_url = wp_nonce_url(
			add_query_arg(
				array(
					'action' => 'anonymise',
					'donor'  => array( $item->id ),
				),
				$this->base_url
			),
			'bulk-donors'
		);

		$donations_url = add_query_arg(
			array(
				'page'     => 'ambassadorhub-donations',
				'donor_id' => $item->id,
			),
			admin_url( 'admin.php' )
		);

		$row_actions = array(
			'donations' => '<a href="' . esc_url( $donations_url ) . '">' . esc_html__( 'Donations', 'ambassadorhub' ) . '</a>',
		);

		if ( ! $item->is_anonymous ) {
			$row_actions['delete'] = '<a href="' . esc_url( $anonymise_url ) . '" onclick="return confirm(\'' . esc_js( __( 'Are you sure you want to anonymise this donor? Their personal data will be replaced with placeholder values.', 'ambassadorhub' ) ) . '\')">' . esc_html__( 'Anonymise', 'ambassadorhub' ) . '</a>';
		}

		return '<strong class="row-title"><span>' . esc_html( $item->name ) . '</span></strong>' . $this->row_actions( $row_actions );
	}

	/**
	 * Donations column — count linked to filtered donations page.
	 *
	 * @param object $item Donor row.
	 * @return string
	 */
	public function column_donations( $item ): string {
		$count = isset( $item->donation_count ) ? (int) $item->donation_count : DonorsModule::count_donations( (int) $item->id );

		if ( $count > 0 ) {
			return '<a href="' . esc_url(
				add_query_arg(
					array(
						'page'     => 'ambassadorhub-donations',
						'donor_id' => $item->id,
					),
					admin_url( 'admin.php' )
				)
			) . '">' . absint( $count ) . '</a>';
		}

		return '0';
	}

	/**
	 * Fallback column renderer.
	 *
	 * @param object $item        Donor row.
	 * @param string $column_name Column slug.
	 * @return string
	 */
	public function column_default( $item, $column_name ): string {
		switch ( $column_name ) {
			case 'email':
				return esc_html( $item->email );
			case 'location':
				$parts = array_filter( array( $item->city ?? '', $item->country ?? '' ) );
				return esc_html( implode( ', ', $parts ) );
			case 'registered':
				return esc_html( date_i18n( get_option( 'date_format' ), strtotime( $item->created_at ) ) );
		}
		return '';
	}
}
