<?php
/**
 * WP_List_Table for wp_conflict_radar_events (F1 — Update History).
 *
 * @package ConflictRadar\Admin
 */

declare( strict_types=1 );

namespace ConflictRadar\Admin;

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

// The base class is not autoloaded; pull it in before extending.
if ( ! class_exists( 'WP_List_Table' ) ) {
	require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}

class UpdateHistoryTable extends \WP_List_Table {

	private const PER_PAGE = 20;

	public function __construct() {
		parent::__construct(
			array(
				'singular' => 'update',
				'plural'   => 'updates',
				'ajax'     => false,
			)
		);
	}

	/**
	 * @return array<string,string>
	 */
	public function get_columns(): array {
		return array(
			'plugin_name'  => __( 'Plugin / Theme', 'conflict-radar' ),
			'version_from' => __( 'From Version', 'conflict-radar' ),
			'version_to'   => __( 'To Version', 'conflict-radar' ),
			'updated_by'   => __( 'Updated By', 'conflict-radar' ),
			'created_at'   => __( 'Date', 'conflict-radar' ),
		);
	}

	/**
	 * @return array<string,array{0:string,1:bool}>
	 */
	protected function get_sortable_columns(): array {
		return array(
			'created_at' => array( 'created_at', true ),
		);
	}

	public function prepare_items(): void {
		global $wpdb;

		// ORDER BY cannot be a prepared placeholder — whitelist instead.
		$allowed = array( 'created_at' => 'created_at' );
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only sort parameter for display ordering; no state change. Validated against a whitelist (Phase 4 injection test).
		$orderby_param = isset( $_GET['orderby'] ) ? sanitize_key( wp_unslash( $_GET['orderby'] ) ) : '';
		$orderby       = $allowed[ $orderby_param ] ?? 'created_at';
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only order parameter for display ordering; no state change. Constrained to ASC/DESC below.
		$order_param = isset( $_GET['order'] ) ? strtoupper( sanitize_text_field( wp_unslash( $_GET['order'] ) ) ) : '';
		$order       = ( 'ASC' === $order_param ) ? 'ASC' : 'DESC';

		$per_page = self::PER_PAGE;
		$current  = $this->get_pagenum();
		$offset   = ( $current - 1 ) * $per_page;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin table (wp_conflict_radar_*); WP options API / object cache not applicable to dedicated event/reporting data.
		$total = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}conflict_radar_events WHERE event_type IN ( 'plugin_update', 'theme_update', 'core_update' )" );

		// $orderby/$order are whitelisted identifiers; only the %d values are dynamic (prepared).
		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is {$wpdb->prefix}; $orderby validated against a fixed column whitelist at line 62; identifier cannot be parameterized. Injection-tested (Phase 4). Value placeholders are prepared. Custom plugin table — caching N/A.
		$rows = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT id, event_type, plugin_name, plugin_slug, version_from, version_to, update_source, triggered_by_user, created_at
				 FROM {$wpdb->prefix}conflict_radar_events
				 WHERE event_type IN ( 'plugin_update', 'theme_update', 'core_update' )
				 ORDER BY {$orderby} {$order}
				 LIMIT %d OFFSET %d",
				$per_page,
				$offset
			),
			ARRAY_A
		);
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter

		$this->items           = $rows ? $rows : array();
		$this->_column_headers = array( $this->get_columns(), array(), $this->get_sortable_columns(), 'created_at' );

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

	/**
	 * @param array<string,mixed> $item
	 */
	public function column_default( $item, $column_name ): string {
		return esc_html( (string) ( $item[ $column_name ] ?? '' ) );
	}

	/**
	 * @param array<string,mixed> $item
	 */
	public function column_plugin_name( $item ): string {
		$name = '' !== (string) $item['plugin_name'] ? (string) $item['plugin_name'] : (string) $item['plugin_slug'];

		/**
		 * Pro attaches the correlation badge after the name via this filter. The free
		 * build has no listener (and no correlation data), so no badge is shown.
		 *
		 * @param string $badge    Badge HTML appended after the name (default empty).
		 * @param int    $event_id The update event row id.
		 */
		return esc_html( $name ) . apply_filters( 'conflict_radar_update_row_badge', '', (int) $item['id'] );
	}

	/**
	 * @param array<string,mixed> $item
	 */
	public function column_updated_by( $item ): string {
		$user_id = (int) $item['triggered_by_user'];
		if ( $user_id > 0 ) {
			$user = get_userdata( $user_id );
			return esc_html( $user ? $user->display_name : (string) $user_id );
		}
		return esc_html__( 'Automatic', 'conflict-radar' );
	}

	/**
	 * @param array<string,mixed> $item
	 */
	public function column_created_at( $item ): string {
		$ts = strtotime( (string) $item['created_at'] . ' UTC' );
		return $ts ? esc_html( wp_date( 'Y-m-d H:i', $ts ) ) : '';
	}

	public function no_items(): void {
		esc_html_e( 'No update events recorded yet.', 'conflict-radar' );
	}
}
