<?php
declare( strict_types=1 );

namespace FiboFilters\Filters\Types;

use FiboFilters\Filters\SourceTypes\SourceTypeInterface;

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

/**
 * Class Filter
 */
abstract class Filter {

	/**
	 * This is the name of this filter type.
	 *
	 * @var string
	 */
	protected $filter_type = 'filter';

	/**
	 * @var string
	 */
	protected $id;

	/**
	 * @var string
	 */
	protected $user_input = '';

	/**
	 * @var array
	 */
	protected $context;

	/**
	 * @var array
	 */
	protected $elementary_query;

	/**
	 * @var array
	 */
	protected $data = [
		'label'                  => '',
		'source'                 => '',
		'source_id'              => 0,
		'type'                   => '',
		'logic'                  => '',
		'desc'                   => '',
		'order'                  => 0,
		'is_collapsible'         => [],
		'is_initially_collapsed' => [],
	];

	/**
	 * @var array
	 */
	protected $values = [];

	/**
	 * @var SourceTypeInterface
	 */
	protected $source_obj;

	public function __construct( string $id ) {
		$this->id = $id;

		$this->extend_data();
	}

	public function get_id(): string {
		return $this->id;
	}

	/**
	 * Get filter data
	 *
	 * @param string $context Context where the data is needed. Values: 'all', 'frontend-filtering'.
	 *
	 * @return array
	 */
	public function get_data( string $context = 'all' ): array {
		$data = $this->get_data_internal();
		$data = $this->filter_data_by_context( $data, $context );

		if ( $context === 'frontend-filtering' ) {
			$data = apply_filters( 'fibofilters/front/filter/get_data/id=' . $this->get_id(), $data );
			$data = apply_filters( 'fibofilters/front/filter/get_data/url_slug=' . $this->get_prop( 'url_slug' ), $data );
		}

		$data = apply_filters( 'fibofilters/filter/get_data', $data, $context );

		// Sanitize tooltip.
		if ( ! empty( $data['tooltip'] ) ) {
			$data['tooltip'] = wp_kses(
				$data['tooltip'],
				[
					'p'      => [],
					'br'     => [],
					'a'      => [
						'href'   => [],
						'target' => [],
					],
					'strong' => [],
					'b'      => [],
					'em'     => [],
					'i'      => [],
					'span'   => [],
				]
			);
		}

		return $data;
	}

	protected function get_data_internal(): array {
		return array_merge( [ 'id' => $this->get_id() ], $this->data, [ 'values' => $this->values ] );
	}

	public function get_data_keys(): array {
		return array_keys( $this->data );
	}

	public function set_props( array $props ): void {
		// TODO zrobic to na wzor \WC_Data::set_props().
		foreach ( $props as $prop => $value ) {
			$this->set_prop( $prop, $value );
		}
	}

	/**
	 * @param string $prop Property name.
	 * @param mixed  $value Propery value.
	 */
	public function set_prop( string $prop, $value ): void {
		$this->data[ $prop ] = $value;
	}

	/**
	 * Gets a prop for a getter method.
	 *
	 * @param string $prop Name of prop to get.
	 *
	 * @return mixed
	 */
	public function get_prop( string $prop ) {
		// TODO zrobic to na wzor \WC_Data::get_prop().
		$value = null;

		if ( array_key_exists( $prop, $this->data ) ) {
			$value = apply_filters( 'fibofilters_' . $this->filter_type . '_get_' . $prop, $this->data[ $prop ], $this );
		}

		return $value;
	}

	/**
	 * Get filter source
	 */
	public function get_source(): string {
		return $this->get_prop( 'source' );
	}

	/**
	 * Get order
	 */
	public function get_order(): int {
		return $this->get_prop( 'order' );
	}

	/**
	 * Prepare and return structure of elementary query
	 */
	public function get_elementary_query(): array {
		$query  = [];
		$values = $this->convert_user_input_to_values();

		if ( empty( $values ) ) {
			return $query;
		}

		return [
			'source' => $this->get_prop( 'source' ),
			'values' => $values,
			'logic'  => $this->get_prop( 'logic' ),
		];
	}

	/**
	 * Get filter type
	 */
	public function get_type(): string {
		return $this->filter_type;
	}

	/**
	 * @param string $value User input value.
	 */
	public function set_user_input( string $value ): void {
		$this->user_input = $value;
	}

	public function get_user_input(): string {
		return $this->user_input;
	}

	/**
	 * @param array $context Context.
	 */
	public function set_context( array $context ): void {
		$this->context = $context;
	}

	/**
	 * @param SourceTypeInterface $source_obj Source type object.
	 */
	public function set_source_obj( SourceTypeInterface $source_obj ): void {
		$this->source_obj = $source_obj;
	}

	/**
	 * Check if filter has selected any values
	 */
	public function is_selected(): bool {
		return $this->user_input !== '';
	}

	/**
	 * Set filter values. Convert, validate and sanitize them.
	 *
	 * @param array $values Raw values from $_GET[] array.
	 */
	public function set_values( array $values ): void {
		$this->values = [];

		$this->before_set_values();

		$values = $this->filter_values( $values );

		foreach ( $values as $value ) {
			$this->add_value( $value );
		}

		$this->after_set_values();
	}

	/**
	 * Update data of specific value
	 *
	 * @param int    $value_id Filter value ID.
	 * @param string $field Field name.
	 * @param mixed  $new_value  New field value.
	 * @param bool   $add Force add new field.
	 */
	public function update_value( int $value_id, string $field, $new_value, bool $add = false ): void {
		if ( empty( $value_id ) ) {
			return;
		}

		foreach ( $this->get_values() as $index => $value ) {
			if ( $value['value_id'] === $value_id ) {
				if ( $add ) {
					$this->values[ $index ][ $field ] = $new_value;
					break;
				} else {
					if ( array_key_exists( $field, $value ) ) {
						$this->values[ $index ][ $field ] = $new_value;
						break;
					}
				}
			}
		}
	}

	/**
	 * Get all filter values
	 */
	public function get_values(): array {
		return $this->values;
	}

	/**
	 * Translate user input to regular values.
	 * This method should be overwritten by inherited classes if the method of processing values is different.
	 */
	public function convert_user_input_to_values(): array {
		return $this->source_obj->validate_user_input( $this->get_user_input(), $this->get_type() );
	}

	protected function before_set_values(): void {
	}

	protected function after_set_values(): void {
	}

	protected function filter_values( array $values ): array {
		return $values;
	}

	/**
	 * Adds single value to the filter
	 *
	 * @param array $value
	 *
	 * @return void
	 */
	protected function add_value( array $value ): void {

		$counter = ! empty( $value['counter'] ) ? (int) $value['counter'] : 0;

		$this->values[] = [
			'value_id'      => (int) $value['value_id'],
			'value'         => $value['value'],
			'parent_value'  => $value['parent_value'],
			'label'         => $value['label'],
			'label_url'     => $value['label_url'],
			'description'   => '',
			'selected'      => false,
			'indeterminate' => false,
			'disabled'      => $counter === 0,
			'counter'       => $counter,
			'group'         => $value['group'] ?? '',
			'default'       => false,
			'order'         => isset( $value['order'] ) ? (int) $value['order'] : 0,
		];
	}

	/**
	 * Maybe overwrite value labels with labels from filter settings
	 *
	 * @return void
	 */
	protected function maybe_overwrite_value_labels(): void {
		$data = $this->get_data();

		if ( empty( $data['value_labels'] ) ) {
			return;
		}

		$org          = wp_list_pluck( $data['value_labels'], 'org' );
		$new          = wp_list_pluck( $data['value_labels'], 'new' );
		$value_labels = array_combine( $org, $new );

		foreach ( $this->get_values() as $value ) {
			if ( is_array( $value_labels ) && isset( $value_labels[ $value['label'] ] ) ) {
				$this->update_value( $value['value_id'], 'label', $value_labels[ $value['label'] ] );
			}
		}
	}

	protected function filter_data_by_context( array $data, string $context = '' ): array {
		$allowed_keys = [];

		if ( $context === 'frontend-filtering' ) {
			$allowed_keys = [
				// Common.
				'id',
				'label',
				'label_url',
				'type',
				'label_in_applied_filters',
				'values',
				'is_collapsible',
				'is_initially_collapsed',
				'url_slug',
				// Category.
				'is_subcategory',
				'parent',
				'current',
				'label_subcategory',
				'label_category',
				'showing_sibling_categories',
				// Range.
				'range',
				'radio_range',
				'slider_range',
				'show_slider',
				'show_radio_ranges',
				'show_inputs',
				'source',
				'source_id',
				'when_to_show',
				'logic',
				'desc',
				'order',
				'user_input_type',
				'orderby',
				'show_zero_values',
				'number_precision',
				'number_prefix',
				'number_suffix',
				'use_global_number_format',
				// Button.
				'show_counters',
				// Color.
				'multiple_selection',
			];
		}

		if ( ! empty( $allowed_keys ) ) {
			$data = array_filter(
				$data,
				function ( string $key ) use ( $allowed_keys ): bool {
					return in_array( $key, $allowed_keys, true );
				},
				ARRAY_FILTER_USE_KEY
			);
		}

		return $data;
	}

	/**
	 * Add additional values to $data.
	 */
	protected function extend_data(): void {
		// To extend in filters.
	}
}
