<?php

namespace SugarCalendarBookings\Admin;

use SugarCalendarBookings\Support\DataProcessing\InputProcessor;
use SugarCalendarBookings\Support\Helpers\FilterLabels;
use SugarCalendarBookings\Support\Helpers\UI;
use WP_List_Table;

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

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

/**
 * Abstract list table.
 *
 * @since 1.2.1
 */
abstract class AbstractListTable extends WP_List_Table {

	/**
	 * Default filter rules for common WordPress list table parameters.
	 *
	 * @since 1.2.1
	 *
	 * @var array
	 */
	protected $default_filter_rules = [
		'page'           => 'string|trim',
		's'              => 'string|trim',
		'status'         => 'string|trim|nullable',
		'order'          => 'string|trim',
		'orderby'        => 'string|trim',
		'post_mime_type' => 'string|trim|nullable',
		'detached'       => 'string|trim|nullable',
	];

	/**
	 * Default filter values for common parameters.
	 *
	 * @since 1.2.1
	 *
	 * @var array
	 */
	protected $default_filters = [
		'page'           => null,
		's'              => null,
		'status'         => null,
		'order'          => 'DESC',
		'orderby'        => 'id',
		'post_mime_type' => null,
		'detached'       => null,
	];

	/**
	 * Domain-specific filter rules (to be defined by child classes).
	 *
	 * @since 1.2.1
	 *
	 * @var array
	 */
	protected $filter_rules = [];

	/**
	 * Domain-specific default filters (to be defined by child classes).
	 *
	 * @since 1.2.1
	 *
	 * @var array
	 */
	protected $active_filters = [];

	/**
	 * Get default filter rules.
	 *
	 * @since 1.2.1
	 *
	 * @return array Default filter rules.
	 */
	protected function get_default_filter_rules() {

		return $this->default_filter_rules;
	}

	/**
	 * Get default filters (merges default + domain-specific).
	 *
	 * @since 1.2.1
	 *
	 * @return array Default filters.
	 */
	protected function get_default_filters() {

		return $this->default_filters;
	}

	/**
	 * Get domain-specific filter rules.
	 *
	 * @since 1.2.1
	 *
	 * @return array Domain-specific filter rules.
	 */
	protected function get_filter_rules() {

		return $this->filter_rules;
	}

	/**
	 * Get all active filters.
	 *
	 * @since 1.2.1
	 *
	 * @return array All active filters.
	 */
	public function get_active_filters() {

		return $this->active_filters;
	}

	/**
	 * Get filter processing rules (merges default + domain-specific).
	 *
	 * @since 1.2.1
	 *
	 * @return array Filter rules for normalization and sanitization.
	 */
	protected function get_all_filter_rules() {

		return array_merge( $this->get_default_filter_rules(), $this->get_filter_rules() );
	}

	/**
	 * Get default filter values (merges default + domain-specific).
	 *
	 * @since 1.2.1
	 *
	 * @return array Default filter values.
	 */
	protected function get_all_filter_defaults() {

		return array_merge( $this->get_default_filters(), $this->get_active_filters() );
	}

	/**
	 * Get a specific active filter value.
	 *
	 * @since 1.2.1
	 *
	 * @param string $key           Filter key to retrieve.
	 * @param mixed  $default_value Default value if filter not set.
	 *
	 * @return mixed Filter value or default.
	 */
	public function get_active_filter( $key, $default_value = null ) {

		$active_filters = $this->get_active_filters();

		return isset( $active_filters[ $key ] ) ? $active_filters[ $key ] : $default_value;
	}

	/**
	 * Get the filter label map for the search results notice.
	 *
	 * Maps active filter keys to display configuration.
	 * Override in child classes to add domain-specific filters.
	 *
	 * Each entry: 'filter_key' => [ 'label' => string, 'model' => string (optional) ].
	 * - If 'model' is set, resolves ID to model name via query.
	 * - If 'model' is absent, uses ucfirst() on the raw value.
	 *
	 * @since 1.4.0
	 *
	 * @return array Filter label map.
	 */
	protected function get_filter_label_map() {

		return [
			'status' => [
				'label' => esc_html__( 'Status', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
			],
		];
	}

	/**
	 * Build resolved filter labels for the search results notice.
	 *
	 * Loops active filters against the label map, resolving model IDs
	 * to display names where configured.
	 *
	 * @since 1.4.0
	 *
	 * @return array Indexed array of resolved filter label entries.
	 */
	protected function get_active_filter_labels() {

		return FilterLabels::resolve( $this->get_active_filters(), $this->get_filter_label_map() );
	}

	/**
	 * Setup active filters by processing request data.
	 *
	 * This method processes $_REQUEST data through normalize and sanitize pipeline,
	 * then merges with default values. Child classes must define filter rules and defaults.
	 *
	 * @since 1.2.1
	 *
	 * @return array Processed filters merged with defaults.
	 */
	protected function setup_active_filters() {

		// Get rules and defaults from child class.
		$rules    = $this->get_all_filter_rules();
		$defaults = $this->get_all_filter_defaults();

		// Step 1: Preprocess comma-separated values into arrays.
		$preprocessed = $this->pre_process_filters( wp_unslash( $_REQUEST ), $rules ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended

		// Step 2: Normalize and sanitize.
		$sanitized = InputProcessor::process_request( $preprocessed, $rules );

		// Step 3: Merge with defaults.
		return $this->post_process_filters( $sanitized, $defaults );
	}

	/**
	 * Preprocess comma-separated values into arrays.
	 *
	 * Detects fields with wildcard rules (e.g., 'tags.*' means 'tags' should be an array)
	 * and converts comma-separated strings to arrays for those fields.
	 *
	 * @since 1.2.1
	 *
	 * @param array $request The request data.
	 * @param array $rules   The filter rules.
	 *
	 * @return array Preprocessed request data.
	 */
	private function pre_process_filters( $request, $rules ) {

		foreach ( $rules as $field => $field_rules ) {

			// Only care about wildcard rules like "tags.*".
			if ( '.*' !== substr( (string) $field, -2 ) ) {
				continue;
			}

			$array_field = substr( (string) $field, 0, -2 );

			if ( $array_field === '' || ! isset( $request[ $array_field ] ) || ! is_string( $request[ $array_field ] ) ) {
				continue;
			}

			$value = trim( $request[ $array_field ] );

			if ( $value === '' ) {
				continue;
			}

			$items = array_map( 'trim', explode( ',', $value ) );
			$items = array_values( array_filter( $items, 'strlen' ) );

			$request[ $array_field ] = $items;
		}

		return $request;
	}

	/**
	 * Use fallback values if sanitized data is empty.
	 *
	 * @since 1.2.1
	 *
	 * @param array $sanitized The sanitized data.
	 * @param array $defaults  The default values.
	 *
	 * @return array The processed data.
	 */
	private function post_process_filters( $sanitized, $defaults ) {

		if ( empty( $sanitized ) ) {
			return $defaults;
		}

		foreach ( $defaults as $key => $value ) {

			if ( ! isset( $sanitized[ $key ] ) || $sanitized[ $key ] === null ) {
				$sanitized[ $key ] = $value;
			}
		}

		return $sanitized;
	}

	/**
	 * Display search result notice.
	 *
	 * @since 1.2.1
	 *
	 * @return void
	 */
	public function search_result_notice() {

		$page = $this->get_active_filter( 'page' );

		UI::search_result_notice(
			[
				'result_count'     => count( $this->items ),
				'search_term'      => $this->get_active_filter( 's' ),
				'close_url'        => admin_url( 'admin.php?page=' . $page ),
				'item_singular'    => ucfirst( $this->_args['singular'] ),
				'item_plural'      => ucfirst( $this->_args['plural'] ),
				'css_class_prefix' => 'sugar-calendar-bookings-calendar',
				'filters'          => $this->get_active_filter_labels(),
			]
		);
	}

	/**
	 * Display search box.
	 *
	 * @since 1.2.1
	 *
	 * @param string $text     Button/label text.
	 * @param string $input_id Search input base ID.
	 *
	 * @return void
	 */
	public function search_box( $text, $input_id ) {

		if ( empty( $this->get_active_filter( 's' ) ) && ! $this->has_items() ) {
			return;
		}

		$this->render_search_box_hidden_fields();

		$this->render_search_box_input( $input_id, $text );
	}

	/**
	 * Render search box hidden fields.
	 *
	 * @since 1.2.1
	 *
	 * @return void
	 */
	protected function render_search_box_hidden_fields() {

		$hidden_fields = [
			'orderby',
			'order',
			'post_mime_type',
			'detached',
		];

		foreach ( $hidden_fields as $field ) {

			$value = $this->get_active_filter( $field );

			if ( ! empty( $value ) ) {
				echo '<input type="hidden" name="' . esc_attr( $field ) . '" value="' . esc_attr( $value ) . '" />';
			}
		}
	}

	/**
	 * Render search box input.
	 *
	 * @since 1.2.1
	 *
	 * @param string $input_id Search input base ID.
	 * @param string $text     Button/label text.
	 *
	 * @return void
	 */
	protected function render_search_box_input( $input_id, $text ) {

		$input_id = $input_id . '-search-input';

		$default_search_text = __( 'Search', 'sugar-calendar-bookings-scheduling-appointments-lite' );

		// Access plural value.
		$placeholder = ! empty( $this->_args['plural'] )
			? $default_search_text . ' ' . ucfirst( $this->_args['plural'] )
			: $default_search_text;

		echo '<p class="search-box">';

		UI::search_input(
			[
				'id'           => $input_id,
				'name'         => 's',
				'value'        => $this->get_active_filter( 's' ),
				'placeholder'  => $placeholder,
				'button_label' => $default_search_text,
			]
		);

		echo '</p>';
	}
}
