<?php
/**
 * Leads worklist built on WP_List_Table.
 *
 * Renders leads read from the leads repository under WooCommerce-style status
 * views (All plus one tab per lifecycle status), defaulting to All so every
 * lead is visible on landing. The Name column carries the nonce-protected row
 * actions: View lead, plus the inbox actions (Promote, Dismiss, Mark as spam)
 * on new leads, Archive on any non-archived lead, and Restore on archived
 * leads. State-changing links are wrapped in wp_nonce_url and gated by the
 * manage-leads capability; their handlers live on the admin page. Every cell is
 * escaped because lead text is untrusted anonymous input (stored-XSS guard).
 *
 * @package WrenchPress
 */

defined( 'ABSPATH' ) || exit;

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

/**
 * Class WrenchPress_Leads_List_Table
 */
class WrenchPress_Leads_List_Table extends WP_List_Table {

	/**
	 * Admin page slug the row actions link back to.
	 */
	const PAGE_SLUG = 'wrenchpress-leads';

	/**
	 * Configure the list table.
	 */
	public function __construct() {
		parent::__construct(
			array(
				'singular' => 'lead',
				'plural'   => 'leads',
				'ajax'     => false,
			)
		);
	}

	/**
	 * Visible columns (UI-SPEC: Name, Phone, Vehicle, Service, Submitted, Status).
	 *
	 * @return array<string,string>
	 */
	public function get_columns() {
		return array(
			'name'      => __( 'Name', 'pitdesk-repair-crm' ),
			'phone'     => __( 'Phone', 'pitdesk-repair-crm' ),
			'vehicle'   => __( 'Vehicle', 'pitdesk-repair-crm' ),
			'service'   => __( 'Service', 'pitdesk-repair-crm' ),
			'submitted' => __( 'Submitted', 'pitdesk-repair-crm' ),
			'status'    => __( 'Status', 'pitdesk-repair-crm' ),
		);
	}

	/**
	 * Sortable columns. The request key maps to the repository sortable map.
	 *
	 * @return array<string,array{0:string,1:bool}>
	 */
	public function get_sortable_columns() {
		return array(
			'name'      => array( 'name', false ),
			'submitted' => array( 'created_at', false ),
		);
	}

	/**
	 * Name is the primary column (carries the row actions).
	 *
	 * @return string
	 */
	protected function get_default_primary_column_name() {
		return 'name';
	}

	/**
	 * WooCommerce-style status views: an "All" tab followed by one tab per lead
	 * lifecycle status (New, Promoted, Dismissed, Spam, Archived). Mirrors the
	 * work-orders list table: each tab carries a live count from the repository
	 * and links back with a 'status' query arg. The default view (no status arg)
	 * is New, so the inbox lands first; the other tabs reach the rest. The current
	 * status request param is sanitized with sanitize_key and only ever compared
	 * against known slugs; it is never interpolated into SQL here.
	 *
	 * @return array<string,string> Map of view id to rendered anchor markup.
	 */
	protected function get_views() {
		$base = add_query_arg( array( 'page' => self::PAGE_SLUG ), admin_url( 'admin.php' ) );
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only status filter from a GET request.
		$current = isset( $_REQUEST['status'] ) ? sanitize_key( wp_unslash( $_REQUEST['status'] ) ) : 'new';
		$counts  = WrenchPress_Leads_Repository::counts_by_status();

		$labels = array(
			'new'       => __( 'New', 'pitdesk-repair-crm' ),
			'promoted'  => __( 'Promoted', 'pitdesk-repair-crm' ),
			'dismissed' => __( 'Dismissed', 'pitdesk-repair-crm' ),
			'spam'      => __( 'Spam', 'pitdesk-repair-crm' ),
			'archived'  => __( 'Archived', 'pitdesk-repair-crm' ),
		);

		$link_data = array(
			'all' => array(
				'url'     => add_query_arg( 'status', 'all', $base ),
				'label'   => __( 'All', 'pitdesk-repair-crm' ),
				'count'   => isset( $counts['all'] ) ? (int) $counts['all'] : 0,
				'current' => 'all' === $current,
			),
		);

		foreach ( WrenchPress_Leads_Repository::statuses() as $slug ) {
			$link_data[ $slug ] = array(
				'url'     => add_query_arg( 'status', $slug, $base ),
				'label'   => isset( $labels[ $slug ] ) ? $labels[ $slug ] : ucfirst( $slug ),
				'count'   => isset( $counts[ $slug ] ) ? (int) $counts[ $slug ] : 0,
				'current' => $slug === $current,
			);
		}

		// The 'count' key is read by core get_views_links() to render the bubble;
		// the WP stub omits it from the accepted shape, hence the ignore.
		return $this->get_views_links( $link_data ); // @phpstan-ignore argument.type
	}

	/**
	 * Empty-state copy (UI-SPEC line 132-133).
	 *
	 * @return void
	 */
	public function no_items() {
		echo '<strong>' . esc_html__( 'No leads yet', 'pitdesk-repair-crm' ) . '</strong><br />';
		echo esc_html__( 'New visit requests from your site form will appear here. Add the visit form to a page to start collecting them.', 'pitdesk-repair-crm' );
	}

	/**
	 * Render the Name column as "Last, First" with the nonce-protected row
	 * actions: View lead, Promote to customer, Dismiss lead, Mark as spam. The
	 * Promote link is the highest-emphasis state-change action; its dedup-aware
	 * handler lives on the admin page.
	 *
	 * @param object $item Lead row.
	 * @return string
	 */
	public function column_name( $item ) {
		$display = sprintf( '%s, %s', (string) $item->last_name, (string) $item->first_name );
		$base    = admin_url( 'admin.php' );
		$actions = array();

		$view_url        = add_query_arg(
			array(
				'page'   => self::PAGE_SLUG,
				'action' => 'view',
				'lead'   => (int) $item->id,
			),
			$base
		);
		$actions['view'] = '<a href="' . esc_url( $view_url ) . '">' . esc_html__( 'View lead', 'pitdesk-repair-crm' ) . '</a>';

		if ( current_user_can( WrenchPress_Capabilities::MANAGE_LEADS ) ) {
			$status      = (string) $item->status;
			$is_archived = ( 'archived' === $status );
			$is_new      = ( 'new' === $status );

			// Promote, Dismiss, and Spam are inbox actions: only offered on a lead
			// that is still 'new'. Once a lead is promoted, dismissed, spam, or
			// archived, these no longer apply and would be confusing on the row.
			if ( $is_new ) {
				// Promote link target: the admin page dispatches on action=promote.
				$promote_url        = wp_nonce_url(
					add_query_arg(
						array(
							'page'   => self::PAGE_SLUG,
							'action' => 'promote',
							'lead'   => (int) $item->id,
						),
						$base
					),
					WrenchPress_Leads_Admin_Page::PROMOTE_NONCE . (int) $item->id
				);
				$actions['promote'] = '<a href="' . esc_url( $promote_url ) . '">' . esc_html__( 'Promote to customer', 'pitdesk-repair-crm' ) . '</a>';

				$dismiss_url        = wp_nonce_url(
					add_query_arg(
						array(
							'page'   => self::PAGE_SLUG,
							'action' => 'dismiss',
							'lead'   => (int) $item->id,
						),
						$base
					),
					'wrenchpress_dismiss_lead_' . (int) $item->id
				);
				$actions['dismiss'] = '<a href="' . esc_url( $dismiss_url ) . '">' . esc_html__( 'Dismiss lead', 'pitdesk-repair-crm' ) . '</a>';

				$spam_url        = wp_nonce_url(
					add_query_arg(
						array(
							'page'   => self::PAGE_SLUG,
							'action' => 'spam',
							'lead'   => (int) $item->id,
						),
						$base
					),
					'wrenchpress_spam_lead_' . (int) $item->id
				);
				$actions['spam'] = '<a href="' . esc_url( $spam_url ) . '" class="submitdelete">' . esc_html__( 'Mark as spam', 'pitdesk-repair-crm' ) . '</a>';
			}

			if ( ! $is_archived ) {
				// Archive is offered on any lead that is not already archived.
				$archive_url        = wp_nonce_url(
					add_query_arg(
						array(
							'page'   => self::PAGE_SLUG,
							'action' => 'archive',
							'lead'   => (int) $item->id,
						),
						$base
					),
					'wrenchpress_archive_lead_' . (int) $item->id
				);
				$actions['archive'] = '<a href="' . esc_url( $archive_url ) . '">' . esc_html__( 'Archive', 'pitdesk-repair-crm' ) . '</a>';
			} else {
				$restore_url        = wp_nonce_url(
					add_query_arg(
						array(
							'page'   => self::PAGE_SLUG,
							'action' => 'restore',
							'lead'   => (int) $item->id,
						),
						$base
					),
					'wrenchpress_restore_lead_' . (int) $item->id
				);
				$actions['restore'] = '<a href="' . esc_url( $restore_url ) . '">' . esc_html__( 'Restore', 'pitdesk-repair-crm' ) . '</a>';
			}
		}

		return '<strong>' . esc_html( $display ) . '</strong>' . $this->row_actions( $actions );
	}

	/**
	 * Render non-primary columns with escaped output (stored-XSS guard).
	 *
	 * @param object $item        Lead row.
	 * @param string $column_name Column key.
	 * @return string
	 */
	public function column_default( $item, $column_name ) {
		switch ( $column_name ) {
			case 'phone':
				return esc_html( (string) $item->phone );

			case 'vehicle':
				$vehicle = trim( (string) $item->make . ' ' . (string) $item->model );
				return esc_html( $vehicle );

			case 'service':
				return esc_html( (string) $item->subject );

			case 'submitted':
				$timestamp = strtotime( (string) $item->created_at );
				return $timestamp ? esc_html( date_i18n( get_option( 'date_format' ), $timestamp ) ) : '';

			case 'status':
				return esc_html( ucfirst( (string) $item->status ) );

			default:
				return '';
		}
	}

	/**
	 * Query leads for the current status view and configure pagination.
	 *
	 * The status request param drives the WooCommerce-style filter: 'all' lists
	 * every status, and any known lifecycle slug (new, promoted, dismissed, spam,
	 * archived) filters to that status. The default (no status arg) is 'new' so
	 * the inbox lands first; an explicit 'all' or any unknown value lists every
	 * lead. The value only picks a code path; it is never interpolated into SQL.
	 *
	 * @return void
	 */
	public function prepare_items() {
		$per_page     = 20;
		$current_page = $this->get_pagenum();
		$offset       = ( $current_page - 1 ) * $per_page;

		// phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only list filters from a GET request.
		$status  = isset( $_REQUEST['status'] ) ? sanitize_key( wp_unslash( $_REQUEST['status'] ) ) : 'new';
		$orderby = isset( $_REQUEST['orderby'] ) ? sanitize_key( wp_unslash( $_REQUEST['orderby'] ) ) : 'created_at';
		$order   = ( isset( $_REQUEST['order'] ) && 'asc' === strtolower( sanitize_key( wp_unslash( $_REQUEST['order'] ) ) ) ) ? 'ASC' : 'DESC';
		// phpcs:enable WordPress.Security.NonceVerification.Recommended

		$args = array(
			'orderby'  => $orderby,
			'order'    => $order,
			'per_page' => $per_page,
			'offset'   => $offset,
		);

		if ( in_array( $status, WrenchPress_Leads_Repository::statuses(), true ) ) {
			$this->items = WrenchPress_Leads_Repository::list_by_status( $status, $args );
			$total       = WrenchPress_Leads_Repository::count_by_status( $status );
		} else {
			$this->items = WrenchPress_Leads_Repository::list_all( $args );
			$total       = WrenchPress_Leads_Repository::count_all();
		}

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

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