<?php
/**
 * WP_List_Table for wp_conflict_radar_logs (F2 — Error Log).
 *
 * @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 ErrorLogTable extends \WP_List_Table {

	private const PER_PAGE = 20;

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

	/**
	 * @return array<string,string>
	 */
	public function get_columns(): array {
		return array(
			'error_type'    => __( 'Type', 'conflict-radar' ),
			'error_message' => __( 'Message', 'conflict-radar' ),
			'error_file'    => __( 'File', 'conflict-radar' ),
			'error_line'    => __( 'Line', 'conflict-radar' ),
			'logged_at'     => __( 'Date', 'conflict-radar' ),
		);
	}

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

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

		// ORDER BY cannot be a prepared placeholder — whitelist instead.
		$allowed = array( 'logged_at' => 'logged_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 ] ?? 'logged_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_logs" );

		// $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 error_type, error_message, error_file, error_line, logged_at
				 FROM {$wpdb->prefix}conflict_radar_logs
				 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(), 'logged_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 ] ?? '' ) );
	}

	/**
	 * Colour-coded type via CSS class (no inline style).
	 *
	 * @param array<string,mixed> $item
	 */
	public function column_error_type( $item ): string {
		$type = (string) $item['error_type'];
		return sprintf(
			'<span class="cr-type-%s">%s</span>',
			esc_attr( $type ),
			esc_html( ucfirst( $type ) )
		);
	}

	/**
	 * @param array<string,mixed> $item
	 */
	public function column_error_message( $item ): string {
		$message = (string) $item['error_message'];
		if ( mb_strlen( $message ) > 100 ) {
			$message = mb_substr( $message, 0, 100 ) . '…';
		}
		return esc_html( $message );
	}

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

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

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