<?php
/**
 * Work Orders list screen built on WP_List_Table.
 *
 * Renders work orders filtered by status, with a get_views() row of status tabs
 * carrying per-status counts (D-18). The status filter is whitelisted against the
 * repository status enum before it reaches any query; it is never interpolated
 * into SQL. Each row resolves its customer and vehicle to display names and shows
 * a colored status badge and the live grand total.
 *
 * @package WrenchPress
 */

defined( 'ABSPATH' ) || exit;

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

/**
 * Class WrenchPress_Work_Orders_List_Table
 */
class WrenchPress_Work_Orders_List_Table extends WP_List_Table {

	/**
	 * Admin page slug the row actions and status tabs link back to.
	 */
	const PAGE_SLUG = 'wrenchpress-work-orders';

	/**
	 * Per-request cache of resolved customer display names, keyed by id.
	 *
	 * @var array<int,string>
	 */
	private $customer_names = array();

	/**
	 * Per-request cache of resolved vehicle labels, keyed by id.
	 *
	 * @var array<int,string>
	 */
	private $vehicle_labels = array();

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

	/**
	 * Visible columns (D-18).
	 *
	 * @return array<string,string>
	 */
	public function get_columns() {
		return array(
			'work_order' => __( 'Work order', 'pitdesk-repair-crm' ),
			'customer'   => __( 'Customer', 'pitdesk-repair-crm' ),
			'vehicle'    => __( 'Vehicle', 'pitdesk-repair-crm' ),
			'status'     => __( 'Status', 'pitdesk-repair-crm' ),
			'total'      => __( 'Total', 'pitdesk-repair-crm' ),
			'updated_at' => __( 'Last updated', 'pitdesk-repair-crm' ),
		);
	}

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

	/**
	 * Work order number is the primary column (carries the row actions).
	 *
	 * @return string
	 */
	protected function get_default_primary_column_name() {
		return 'work_order';
	}

	/**
	 * Status-tab views with per-status counts (D-18).
	 *
	 * Builds an "All" tab plus one tab per status slug, each carrying its live
	 * count from the repository, and hands the link data to the core
	 * get_views_links() helper which renders the anchors and counts. 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'] ) ) : 'all';
		$counts  = WrenchPress_Work_Orders_Repository::counts_by_status();

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

		foreach ( WrenchPress_Work_Orders_Repository::statuses() as $slug => $label ) {
			$link_data[ $slug ] = array(
				'url'     => add_query_arg( 'status', $slug, $base ),
				'label'   => $label,
				'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's signature omits it, so the array shape is narrowed here.
		return $this->get_views_links( $link_data ); // @phpstan-ignore argument.type
	}

	/**
	 * Empty-state copy.
	 *
	 * @return void
	 */
	public function no_items() {
		esc_html_e( 'No work orders in this view yet.', 'pitdesk-repair-crm' );

		if ( current_user_can( WrenchPress_Capabilities::MANAGE_WORKORDERS ) ) {
			$add_url = add_query_arg(
				array(
					'page'   => self::PAGE_SLUG,
					'action' => 'add',
				),
				admin_url( 'admin.php' )
			);
			echo ' <a href="' . esc_url( $add_url ) . '" class="button button-primary">' . esc_html__( 'Add Work Order', 'pitdesk-repair-crm' ) . '</a>';
		}
	}

	/**
	 * Render the Work order column as "#<id>" with a View row action.
	 *
	 * @param object $item Work-order row.
	 * @return string
	 */
	public function column_work_order( $item ) {
		$wo_id   = (int) $item->id;
		$base    = admin_url( 'admin.php' );
		$actions = array();

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

		return '<strong><a href="' . esc_url( $view_url ) . '">' . esc_html( '#' . $wo_id ) . '</a></strong>' . $this->row_actions( $actions );
	}

	/**
	 * Render non-primary columns with escaped output.
	 *
	 * @param object $item        Work-order row.
	 * @param string $column_name Column key.
	 * @return string
	 */
	public function column_default( $item, $column_name ) {
		switch ( $column_name ) {
			case 'customer':
				return $this->customer_cell( (int) $item->customer_id );

			case 'vehicle':
				return esc_html( $this->vehicle_label( (int) $item->vehicle_id ) );

			case 'status':
				$statuses = WrenchPress_Work_Orders_Repository::statuses();
				$slug     = (string) $item->status;
				$label    = isset( $statuses[ $slug ] ) ? $statuses[ $slug ] : $slug;
				return '<span class="wrenchpress-status wrenchpress-status--' . esc_attr( $slug ) . '">' . esc_html( $label ) . '</span>';

			case 'total':
				$totals = WrenchPress_Work_Order_Items_Repository::totals( (int) $item->id, $item->tax_rate );
				return esc_html( number_format( (float) $totals['grand_total'], 2 ) );

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

			default:
				return '';
		}
	}

	/**
	 * Render the customer cell: the display name linked to the customer detail
	 * view when the current user can view customers, otherwise plain text.
	 *
	 * @param int $customer_id Customer id.
	 * @return string
	 */
	private function customer_cell( $customer_id ) {
		$customer_id = (int) $customer_id;
		$name        = $this->customer_name( $customer_id );

		if ( '' === $name ) {
			return '';
		}

		if ( ! current_user_can( WrenchPress_Capabilities::VIEW_CUSTOMERS ) ) {
			return esc_html( $name );
		}

		$view_url = add_query_arg(
			array(
				'page'     => WrenchPress_Customers_List_Table::PAGE_SLUG,
				'action'   => 'view',
				'customer' => $customer_id,
			),
			admin_url( 'admin.php' )
		);

		return '<a href="' . esc_url( $view_url ) . '">' . esc_html( $name ) . '</a>';
	}

	/**
	 * Resolve a customer id to a "Last, First" display name, cached per request.
	 *
	 * @param int $customer_id Customer id.
	 * @return string
	 */
	private function customer_name( $customer_id ) {
		$customer_id = (int) $customer_id;

		if ( 0 === $customer_id ) {
			return '';
		}

		if ( isset( $this->customer_names[ $customer_id ] ) ) {
			return $this->customer_names[ $customer_id ];
		}

		$customer = WrenchPress_Customers_Repository::get( $customer_id );
		$name     = $customer ? trim( sprintf( '%s, %s', $customer->last_name, $customer->first_name ), ', ' ) : '';

		$this->customer_names[ $customer_id ] = $name;

		return $name;
	}

	/**
	 * Resolve a vehicle id to a "Year Make Model" label, cached per request.
	 *
	 * @param int $vehicle_id Vehicle id.
	 * @return string
	 */
	private function vehicle_label( $vehicle_id ) {
		$vehicle_id = (int) $vehicle_id;

		if ( 0 === $vehicle_id ) {
			return '';
		}

		if ( isset( $this->vehicle_labels[ $vehicle_id ] ) ) {
			return $this->vehicle_labels[ $vehicle_id ];
		}

		$vehicle = WrenchPress_Vehicles_Repository::get( $vehicle_id );
		$label   = $vehicle ? WrenchPress_Vehicles_List_Table::vehicle_label( $vehicle ) : '';

		$this->vehicle_labels[ $vehicle_id ] = $label;

		return $label;
	}

	/**
	 * Query work orders for the current status view and configure pagination.
	 *
	 * The status request param is whitelisted against the repository status enum;
	 * 'all' (or an unknown value) lists every status, a known slug filters to it.
	 * The value is only ever used to pick a code path, never interpolated.
	 *
	 * @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'] ) ) : 'all';
		$orderby = isset( $_REQUEST['orderby'] ) ? sanitize_key( wp_unslash( $_REQUEST['orderby'] ) ) : 'updated_at';
		$order   = ( isset( $_REQUEST['order'] ) && 'asc' === strtolower( sanitize_key( wp_unslash( $_REQUEST['order'] ) ) ) ) ? 'ASC' : 'DESC';
		// phpcs:enable WordPress.Security.NonceVerification.Recommended

		$statuses = WrenchPress_Work_Orders_Repository::statuses();

		// Whitelist: only 'all' or a known status slug is allowed; anything else
		// collapses to 'all' so a stray value can never widen or distort the query.
		if ( 'all' !== $status && ! array_key_exists( $status, $statuses ) ) {
			$status = 'all';
		}

		$args = array(
			'orderby'  => array_key_exists( $orderby, WrenchPress_Work_Orders_Repository::sortable_map() ) ? $orderby : 'created_at',
			'order'    => $order,
			'per_page' => $per_page,
			'offset'   => $offset,
		);

		if ( 'all' === $status ) {
			$this->items = $this->list_all( $args );
			$total       = $this->count_all();
		} else {
			$this->items = WrenchPress_Work_Orders_Repository::list_by_status( $status, $args );
			$total       = WrenchPress_Work_Orders_Repository::count_by_status( $status );
		}

		$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 ),
			)
		);
	}

	/**
	 * List work orders across every status for the "All" view.
	 *
	 * Delegates to the repository, which queries all statuses in a single
	 * prepared SELECT with SQL-side ORDER BY, LIMIT, and OFFSET. This avoids
	 * loading every row into PHP and the previous silent per-status cap, so the
	 * rows shown always agree with the pagination total.
	 *
	 * @param array $args orderby, order, per_page, offset.
	 * @return array List of row objects for the requested page.
	 */
	private function list_all( array $args ) {
		return WrenchPress_Work_Orders_Repository::list_all( $args );
	}

	/**
	 * Count work orders across every status for the "All" view.
	 *
	 * @return int
	 */
	private function count_all() {
		return WrenchPress_Work_Orders_Repository::count_all();
	}
}
