<?php

namespace GP_Entry_Blocks;

use GFFormsModel;
use GFCommon;
use GFAPI;

/**
 * @since 1.0
 */
class Entry_Filters {
	public function get_entry_property_groups() {
		return array(
			'fields' => array(
				'label' => esc_html__( 'Fields', 'gp-entry-blocks' ),
			),
			'meta'   => array(
				'label' => esc_html__( 'Entry Meta', 'gp-entry-blocks' ),
			),
		);
	}

	/**
	 * @todo Use this for Summary Columns to reduce duplication and increase extensibility.
	 *
	 * @param $form_id
	 *
	 * @return array|array[]
	 */
	public function get_entry_properties( $form_id ) {
		if ( ! $form_id ) {
			return array();
		}

		$properties = array(
			'id'             => array(
				'label'    => esc_html__( 'Entry ID', 'gp-entry-blocks' ),
				'value'    => 'id',
				'callable' => array( $this, 'get_col_rows' ),
				'args'     => array( GFFormsModel::get_entry_table_name(), 'id' ),
				'orderby'  => true,
			),
			'created_by'     => array(
				'label'    => esc_html__( 'Created by (User ID)', 'gp-entry-blocks' ),
				'value'    => 'created_by',
				'callable' => '__return_empty_array',
				'orderby'  => true,
			),
			'date_created'   => array(
				'label'    => esc_html__( 'Date Created', 'gp-entry-blocks' ),
				'value'    => 'date_created',
				'callable' => '__return_empty_array',
				'orderby'  => true,
			),
			'date_updated'   => array(
				'label'    => esc_html__( 'Date Updated', 'gp-entry-blocks' ),
				'value'    => 'date_updated',
				'callable' => '__return_empty_array',
				'orderby'  => true,
			),
			'ip'             => array(
				'label'    => esc_html__( 'IP', 'gp-entry-blocks' ),
				'value'    => 'ip',
				'callable' => '__return_empty_array',
				'orderby'  => true,
			),
			'payment_method' => array(
				'label'    => esc_html__( 'Payment Method', 'gp-entry-blocks' ),
				'value'    => 'payment_method',
				'callable' => array( $this, 'get_col_rows' ),
				'args'     => array( GFFormsModel::get_entry_table_name(), 'payment_method' ),
				'orderby'  => true,
			),
			'payment_status' => array(
				'label'    => esc_html__( 'Payment Status', 'gp-entry-blocks' ),
				'value'    => 'payment_status',
				'callable' => array( $this, 'get_col_rows' ),
				'args'     => array( GFFormsModel::get_entry_table_name(), 'payment_status' ),
				'orderby'  => true,
			),
			'status'         => array(
				'label'    => esc_html__( 'Status', 'gp-entry-blocks' ),
				'value'    => 'status',
				'callable' => array( $this, 'get_col_rows' ),
				'args'     => array( GFFormsModel::get_entry_table_name(), 'status' ),
				'orderby'  => true,
			),
			'transaction_id' => array(
				'label'    => esc_html__( 'Transaction ID', 'gp-entry-blocks' ),
				'value'    => 'transaction_id',
				'callable' => '__return_empty_array',
				'orderby'  => true,
			),
			'is_starred'     => array(
				'label'    => esc_html__( 'Is Starred', 'gp-entry-blocks' ),
				'value'    => 'is_starred',
				'callable' => array( $this, 'get_is_starred_values' ),
				'orderby'  => true,
			),
		);

		foreach ( $this->get_form_fields( $form_id ) as $form_field ) {
			$properties[ $form_field['value'] ] = array(
				'value'    => $form_field['value'],
				'group'    => 'fields',
				'label'    => $form_field['label'],
				'callable' => array( $this, 'get_form_fields_values' ),
				'args'     => array( $form_id, $form_field['value'] ),
				'orderby'  => rgar( $form_field, 'orderby', false ),
			);
		}

		return $properties + $this->get_entry_meta_properties( $form_id );
	}

	/**
	 * Returns the possible values for the `is_starred` entry column.
	 *
	 * @todo Make this an associative array with a label for each value to improve UX. e.g. 0 = 'Not Starred'
	 * Depends on `<FilterValueControl />` supporting associative arrays for property values.
	 */
	public function get_is_starred_values() {
		return array( 0, 1 );
	}

	/**
	 * @param number $form_id
	 * @param string $property_id
	 *
	 * @return array
	 */
	public function get_entry_property_values( $form_id, $property_id ) {
		if ( ! $form_id || ! $property_id ) {
			return array();
		}

		$properties = $this->get_entry_properties( $form_id );
		$property   = rgar( $properties, $property_id );

		if ( ! $property ) {
			return array();
		}

		$property_args = rgar( $property, 'args', array() );

		$output = call_user_func_array( $property['callable'], $property_args );

		/**
		 * Transform associative array to flattened array
		 */
		if ( is_assoc_array( $output ) ) {
			natcasesort( $output );

			$non_associative_output = array();

			foreach ( $output as $value => $label ) {
				$non_associative_output[] = array( $value, $label );
			}

			$output = $non_associative_output;
		} else {
			natcasesort( $output );
		}

		/* Remove duplicate property values */
		$output = array_unique( $output, SORT_REGULAR );

		/* Ensure keys are in numeric order otherwise when JSON encoding, it will be an object rather than array. */
		return array_values( $output );
	}

	/**
	 * Some field types such as time handle the value as a single value rather than a value for each input.
	 *
	 * @return array
	 */
	public static function get_interpreted_multi_input_field_types() {
		/**
		 * Filter the field types that are flagged as interpreted multi input field types.
		 *
		 * @param array $field_types An array of field types.
		 */
		return apply_filters(
			'gpeb_interpreted_multi_input_field_types',
			array(
				'time',
				'date',
			)
		);
	}


	public function get_form_fields( $form_id ) {

		$form = GFAPI::get_form( $form_id );

		if ( ! $form || ! $form_id ) {
			return array();
		}

		$output = array();

		foreach ( $form['fields'] as $field ) {
			if ( $field->displayOnly ) {
				continue;
			}

			/**
			 * Use admin label when listing out fields
			 */
			$use_admin_label_prev = $field->get_context_property( 'use_admin_label' );
			$field->set_context_property( 'use_admin_label', true );

			if ( empty( $field['inputs'] ) || in_array( $field['type'], $this->get_interpreted_multi_input_field_types(), true ) ) {
				$output[] = array(
					'value'   => $field['id'],
					'label'   => GFCommon::get_label( $field ),
					'orderby' => true,
				);
			} elseif ( is_array( $field['inputs'] ) ) {
				$output[] = array(
					'value'   => $field['id'],
					'label'   => GFCommon::get_label( $field ),
					'orderby' => false,
				);

				foreach ( $field['inputs'] as $input ) {
					$output[] = array(
						'value'   => $input['id'],
						'label'   => GFCommon::get_label( $field, $input['id'] ),
						'orderby' => true,
					);
				}
			}

			$field->set_context_property( 'use_admin_label', $use_admin_label_prev );
		}

		return $output;

	}

	public function get_form_fields_values( $form_id, $input_id ) {

		global $wpdb;

		$entry_meta_table = GFFormsModel::get_entry_meta_table_name();

		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		return $wpdb->get_col( $wpdb->prepare( "SELECT meta_value from $entry_meta_table WHERE form_id = %d AND meta_key = %s", $form_id, $input_id ) );

	}

	public function get_col_rows( $table, $col ) {

		static $_cache;

		global $wpdb;

		/**
		 * Filter the query used to pull property values into the Entry Filters control.
		 *
		 * @since 1.0
		 *
		 * @param string $sql SQL query that will be ran to fetch the property values.
		 * @param string $col Column that property values are being fetched from.
		 */
		$query = apply_filters( 'gpeb_filters_col_rows_query', "SELECT DISTINCT $col FROM $table LIMIT 1000", $col, $table );

		if ( isset( $_cache[ $query ] ) ) {
			return $_cache[ $query ];
		}

		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
		$result = $wpdb->get_col( $query );

		$_cache[ $query ] = is_array( $result ) ? $this->filter_values( $result ) : array();

		return $_cache[ $query ];

	}

	public function get_entry_meta_properties( $form_id ) {

		$properties = array();

		foreach ( GFFormsModel::get_entry_meta( $form_id ) as $meta_key => $meta ) {
			$properties[ $meta_key ] = array(
				'value'    => $meta_key,
				'group'    => 'meta',
				'label'    => $meta['label'],
				'callable' => array( $this, 'get_form_fields_values' ),
				'args'     => array( $form_id, $meta_key ),
				'orderby'  => true,
			);
		}

		// Add in unregistered meta keys
		$unregistered_meta_keys = $this->get_col_rows( GFFormsModel::get_entry_meta_table_name(), 'meta_key' );

		foreach ( $unregistered_meta_keys as $meta_key ) {
			// Skip registered meta
			if ( isset( $properties[ $meta_key ] ) ) {
				continue;
			}

			// Skip fields
			if ( is_numeric( $meta_key ) ) {
				continue;
			}

			$properties[ $meta_key ] = array(
				'value'    => $meta_key,
				'group'    => 'meta',
				'label'    => $meta_key,
				'callable' => '__return_empty_array',
				'orderby'  => true,
			);
		}

		return $properties;

	}


	/*
	 * array_filter - Remove serialized values
	 * array_filter - Remove falsey values
	 * array_unique - Ran to make sequential for json_encode
	 */
	public function filter_values( $values ) {

		$values = array_values(
			array_unique(
				array_filter(
					array_filter(
						$values,
						array(
							__class__,
							'is_not_serialized',
						)
					)
				)
			)
		);

		natcasesort( $values );

		/* Run array values again so it's an ordered indexed array again */
		return array_values( $values );

	}

	public static function is_not_serialized( $value ) {
		return ! is_serialized( $value );
	}

}
