<?php
namespace SabaiApps\Directories\Component\View\Helper;

use SabaiApps\Directories\Application;
use SabaiApps\Directories\Exception;
use SabaiApps\Directories\Component\Entity;
use SabaiApps\Directories\Component\View\Model;
use SabaiApps\Directories\Component\Form;
use SabaiApps\Directories\Component\Field;
use SabaiApps\Directories\Component\Display\Element\AbstractElement;

class FilterFormHelper {

	public function help( Application $application, $bundleName, ?Entity\Type\Query $query = null, $displayName = 'default', array $options = array() ) {
		if ( ! $bundle = $application->Entity_Bundle( $bundleName ) ) {
			throw new Exception\RuntimeException( 'Invalid bundle: ' . $bundleName );
		}

		if ( ! $application->getComponent( 'View' )->isFilterable( $bundle ) ) {
			return;
		}

		// Get filters
		$filters = $application->Filter(
			'view_filter_form_filters',
			isset( $options['filters'] )
				? $options['filters']
				: $application->getModel( 'Filter', 'View' )->bundleName_is( $bundleName )->displayName_is( $displayName )->fetch()->with( 'Field', 'FieldConfig' )->getArray( null, 'name' ),
			array( $bundle )
		);
		if ( empty( $filters ) ) {
			return;
		}

		// Init options
		$filter_config = $application->getComponent( 'View' )->getConfig( 'filters' );
		$options      += array(
			'method'             => 'post',
			'url'                => null,
			'attributes'         => array(),
			'values'             => null,
			'container'          => null,
			'push_state'         => false,
			'auto_submit'        => ! isset( $filter_config['auto_submit'] ) || $filter_config['auto_submit'],
			'current'            => null,
			'btn_label'          => __( 'Apply Filters', 'directories-pro' ),
			'btn_size'           => 'lg',
			'btn_color'          => 'primary',
			'btn_block'          => true,
			'enable_facet_count' => false,
			'button'             => array(),
		);

		// Init form
		$form = array(
			'#method'            => $options['method'],
			'#class'             => 'drts-view-filter-form',
			'#attributes'        => $options['attributes'],
			'#token'             => false,
			'#build_id'          => false,
			'#inherits'          => array( 'entity_filter_form' ),
			'#bundle'            => $bundle,
			'#display_name'      => $displayName,
			'#js_ready_fallback' => true,
			'#js_ready'          => array( 'DRTS.View.filterForm("#__FORM_ID__");' ),
			'#filters'           => array(),
			'#hidden_params'     => array(), // hidden values converted from URL params
		);
		if ( empty( $options['auto_submit'] ) ) {
			$form['#class'] .= ' drts-view-filter-form-manual';
		}
		if ( isset( $options['container'] ) ) {
			$form['#attributes']['data-entities-container'] = $options['container'];
		}
		$form['#attributes'] += array(
			'data-push-state' => ( ! defined( 'DRTS_FIX_URI_TOO_LONG' ) || ! DRTS_FIX_URI_TOO_LONG ) && ! empty( $options['push_state'] ) ? 1 : 0,
		);
		if ( ! $options['push_state'] ) {
			$form['#attributes']['autocomplete'] = 'off';
		}
		// Convert url params to hidden values, mainly used to unset search form values
		if ( isset( $options['url'] ) ) {
			$url = $application->Url( $options['url'] );
			unset( $url->params['sort'], $url->params['view'], $url->params['num'] );
			$this->_addHiddenValues( $form, $url->params );
			$form['#hidden_params'] = array_keys( $url->params );
			$url->params            = array();
			$form['#action']        = $url;
		}
		// Add "filter" parameter required to invoke filters
		$form['filter']           = array(
			'#type'  => 'hidden',
			'#value' => 1,
		);
		$form['#hidden_params'][] = 'filter';
		// Add buttons
		$buttons = array(
			$options['button'] + array(
				'#btn_label'  => $options['btn_label'],
				'#btn_size'   => $options['btn_size'],
				'#btn_color'  => $options['btn_color'],
				'#btn_block'  => ! empty( $options['btn_block'] ),
				'#attributes' => array( 'class' => 'drts-view-filter-form-submit' ),
			),
		);
		$form[ Form\FormComponent::FORM_SUBMIT_BUTTON_NAME ] = $application->Form_Form_submitButtons( $buttons );

		// Add filters
		foreach ( $filters as $filter_name => $filter ) {
			if ( ! $field = $filter->getField() ) {
				$filter->markRemoved()->commit();
				continue;
			}

			if ( $field->getFieldData( 'disabled' ) ) {
				continue;
			}

			if ( $filter_form = $this->_getField(
				$application,
				$filter,
				$query,
				isset( $options['values'][ $filter_name ] ) ? $options['values'][ $filter_name ] : null,
				isset( $options['current'][ $filter_name ] ) ? $options['current'][ $filter_name ] : null,
				! empty( $options['auto_submit'] ),
				! empty( $options['enable_facet_count'] )
			) ) {
				$form[ $filter_name ]                              = $filter_form;
				$form['#filters'][ $filter->type ][ $filter_name ] = $filter;
			}
		}

		if ( empty( $form['#filters'] ) ) {
			return;
		}

		$application->getPlatform()->addJsFile( 'view-filter-form.min.js', 'drts-view-filter-form', array( 'drts' ) );

		return $form;
	}

	protected function _addHiddenValues( &$form, $params ) {
		foreach ( array_keys( $params ) as $key ) {
			if ( is_array( $params[ $key ] ) ) {
				$form[ $key ] = array(
					'#tree' => true,
				);
				$this->_addHiddenValues( $form[ $key ], $params[ $key ] );
			} else {
				$form[ $key ] = array(
					'#type'  => 'hidden',
					'#value' => $params[ $key ],
				);
			}
		}
	}

	public function render( Application $application, $form, $columns = null, $formTag = 'form' ) {
		if ( is_string( $form ) ) {
			return $form; // may already have rendered
		}

		if ( is_array( $form ) ) {
			$form = $application->Form_Form_build( $form );
		}

		if ( ! $form instanceof Form\Form ) {
			throw new Exception\InvalidArgumentException( 'Invalid form.' );
		}

		$form    = $form->render();
		$display = $this->getDisplay( $application, $form->settings['#bundle'], $form->settings['#display_name'] );
		// Extract headers from display elements
		$headers = array();
		foreach ( array_keys( $display['elements'] ) as $element_id ) {
			$element =& $display['elements'][ $element_id ];
			if ( isset( $element['heading']['label'] ) ) {
				$headers[ $element_id ] = $application->Display_Render_elementLabel(
					$element['heading'],
					AbstractElement::stringId( $form->settings['#bundle']->name, $element['display'], $element['name'], $element['_element_id'], 'label' )
				);
				unset( $element['heading'] );
			} elseif ( ! empty( $element['settings']['label_as_heading'] ) ) {
				$form_field_label = '';
				if ( ! empty( $element['settings']['field_name'] )
					&& ( $field = $application->Entity_Field( $form->settings['#bundle'], $element['settings']['field_name'] ) )
				) {
					$form_field_label = $field->getFieldLabel( true );
				}
				$headers[ $element_id ]       = $application->Display_Render_elementLabel(
					$element['settings'],
					AbstractElement::stringId( $form->settings['#bundle']->name, $element['display'], $element['name'], $element['_element_id'], 'label' ),
					$form_field_label
				);
				$element['settings']['label'] = 'none';
			}
		}

		$options  = array(
			'tag'           => null,
			'html_as_array' => true,
		);
		$rendered = $application->Display_Render( $form->settings['#bundle'], $display, $form, $options );
		if ( ! $rendered || ! $rendered['html'] ) {
			return '';
		}

		$html = '<div' . $application->Attr( $rendered['attr'] ) . '><div class="drts-row drts-gutter-none">';
		foreach ( $rendered['html'] as $element_id => $column ) {
			$html .= '<div class="drts-col-12 drts-col-sm-6 drts-col-lg-4 drts-col-xl-3 drts-col-xxl-2 drts-grow">';
			$html .= '<div class="drts-view-filter-form-card ' . DRTS_BS_PREFIX . 'card ' . DRTS_BS_PREFIX . 'h-100" data-display-element-id="' . $application->H( $element_id ) . '">';
			if ( isset( $headers[ $element_id ] )
				&& strlen( $headers[ $element_id ] )
			) {
				$html .= '<div class="' . DRTS_BS_PREFIX . 'card-header ' . DRTS_BS_PREFIX . 'position-relative">' . $headers[ $element_id ]
					. '<span class="drts-view-filter-form-close-filter ' . DRTS_BS_PREFIX . 'position-absolute ' . DRTS_BS_PREFIX . 'top-50 ' . DRTS_BS_PREFIX . 'end-0 ' . DRTS_BS_PREFIX . 'translate-middle ' . DRTS_BS_PREFIX . 'me-2">'
					. '<i class="fas fa-chevron-down"></i></span></div>';
			}
			$html .= '<div class="' . DRTS_BS_PREFIX . 'card-body">' . $column . '</div>';
			$html .= '</div></div>';
		}
		$html .= '</div></div>';

		// Manually render hidden param values here since they were added dynamically and are not rendered by the display
		$hidden_values = '';
		foreach ( $form->settings['#hidden_params'] as $param ) {
			$hidden_values .= $form->getHtml( $param );
		}

		return implode(
			PHP_EOL,
			array(
				$form->getHeaderHtml(),
				$form->getFormTag( null, $formTag ),
				$html,
				$form->getHtml( Form\FormComponent::FORM_SUBMIT_BUTTON_NAME ),
				$form->getHiddenHtml(),
				$hidden_values,
				'</' . $formTag . '>',
				$form->getJsHtml(),
			)
		);
	}

	public function getDisplay( Application $application, Entity\Model\Bundle $bundle, $name = 'default', $useCache = true, $checkFields = false ) {
		if ( ! $display = $application->Display_Display( $bundle->name, $name, 'filters', $useCache ) ) {
			$elements    = array();
			$field_types = $application->Field_Types();
			foreach ( $application->getModel( 'Filter', 'View' )->bundleName_is( $bundle->name )->displayName_is( $name )->fetch()->with( 'Field', 'FieldConfig' ) as $filter ) {
				if ( ( ! $ifilter = $application->Field_Filters_impl( $filter->type, true ) )
					|| ( ! $field = $filter->getField() )
					|| ! isset( $field_types[ $field->getFieldType() ] )
					|| empty( $field_types[ $field->getFieldType() ]['filters'] )
				) {
					continue;
				}

				$elements[] = array(
					'name'   => 'view_filter_' . $field->getFieldName(),
					'weight' => (int) @$filter->data['weight'],
					'system' => ! $filter->isCustomFilter(),
					'data'   => array(
						'title'    => $field->getFieldLabel(),
						'settings' => array(
							'filter'      => $filter->type,
							'filter_name' => $filter->name,
						),
					),
				);
			}
			$application->Display_Create(
				$bundle,
				'filters',
				$name,
				array(
					'system'   => true,
					'elements' => $elements,
				)
			);

			// Attempt to fetch the display again
			if ( ! $display = $application->Display_Display( $bundle->name, $name, 'filters', false ) ) {
				throw new Exception\RuntimeException( 'Failed loading filters display for ' . $bundle->name );
			}
		} elseif ( $checkFields ) {
				$reload_required = false;

				// Get current element names in display
				$elements = array();
			foreach ( array_keys( $display['elements'] ) as $element_id ) {
				$this->_getRecursiveElements( $display['elements'][ $element_id ], $elements );
			}
				// Create field element if the element does not exist in display
				$elements_required = $this->_getValidDisplayElements( $application, $bundle, $name );
			foreach ( array_keys( $elements_required ) as $filter_name ) {
				if ( ! isset( $elements[ $filter_name ] ) ) {
					// The field element does not exist in display, so create it
					$application->Display_Create_element( $bundle, $display['name'], $elements_required[ $filter_name ], 'filters' );

					$reload_required = true;
				} else {
					unset( $elements[ $filter_name ] );
				}
			}
				// Remove elements and its filters that are no longer valid. This happens for example when the field associated with a filter has been deleted.
			if ( ! empty( $elements ) ) {
				foreach ( $elements as $element_id ) {
					// Do not notify display element implementation since the field associated with the element most likely does not exist
					// and the implementation as well no longer exists which will cause a runtime exception to be thrown.
					// It is safe to not notify since the filter associated has been removed already.
					$notify = false;
					$application->Display_AdminElement_delete( $bundle, $element_id, $notify );
					unset( $display['elements'][ $element_id ] );

					$reload_required = true;
				}
			}

				// Attempt to fetch the display again if reload required
			if ( $reload_required ) {
				if ( ! $display = $application->Display_Display( $bundle->name, $name, 'filters', false ) ) {
					throw new Exception\RuntimeException( 'Failed loading filter form display for ' . $bundle->name );
				}
			}
		}

		return $display;
	}

	protected function _getRecursiveElements( $element, &$names ) {
		if ( $element['type'] === 'field' ) {
			$names[ $element['settings']['filter_name'] ] = $element['id'];
		}
		if ( ! empty( $element['children'] ) ) {
			foreach ( array_keys( $element['children'] ) as $element_id ) {
				$this->_getRecursiveElements( $element['children'][ $element_id ], $names );
			}
		}
	}

	protected function _getValidDisplayElements( Application $application, Entity\Model\Bundle $bundle, $displayName ) {
		$elements = array();
		foreach ( $application->getModel( 'Filter', 'View' )->bundleName_is( $bundle->name )->displayName_is( $displayName )->fetch()->with( 'Field', 'FieldConfig' ) as $filter ) {
			if ( ! $element = $filter->toDisplayElementArray() ) {
				// Field for the filter no longer exists, so manually remove filter
				$filter->markRemoved()->commit();
				continue;
			}

			$elements[ $filter->name ] = $element;
			// Need ID to use the current filter
			$elements[ $filter->name ]['data']['settings']['filter_id'] = $filter->id;
		}

		return $elements;
	}

	protected function _getField( Application $application, Model\Filter $filter, ?Entity\Type\Query $query = null, $value = null, ?array $current = null, $autoSubmit = true, $enableFacetCount = false ) {
		if ( isset( $current ) ) {
			if ( ! $enableFacetCount ) {
				return $current;
			}
			if ( ! $ifilter = $application->Field_Filters_impl( $filter->type, true ) ) {
				return;
			}

			if ( ! $ifilter->fieldFilterInfo( 'facetable' ) ) {
				return $current;
			}
		} elseif ( ! $ifilter = $application->Field_Filters_impl( $filter->type, true ) ) {
				return;
		}
		$settings = $filter->data['settings'] + (array) $ifilter->fieldFilterInfo( 'default_settings' );
		if ( ( ! $field = $filter->getField() )
			|| ( ! $filter_form = $ifilter->fieldFilterForm( $field, $filter->name, $settings, $value, $query, $current, $autoSubmit, $enableFacetCount, array( $filter->name ) ) )
		) {
			return;
		}

		$filter_form['#tree']                     = true;
		$filter_form['#weight']                   = isset( $filter->data['weight'] ) ? $filter->data['weight'] : 0;
		$filter_form['#data']['view-filter-name'] = $filter->name;
		if ( isset( $filter_form['#entity_filter_form_type'] ) ) {
			$filter_form['#data']['view-filter-form-type'] = $filter_form['#entity_filter_form_type'];
		}
		$class = 'drts-view-filter-form-field drts-view-filter-form-field-type-' . str_replace( '_', '-', $filter->type );
		if ( isset( $filter_form['#class'] ) ) {
			$filter_form['#class'] .= ' ' . $class;
		} else {
			$filter_form['#class'] = $class;
		}
		// Any conditional rules?
		if ( $states = $this->_getFilterStates( $application, $filter ) ) {
			$filter_form['#states']          = $states;
			$filter_form['#states_selector'] = '#drts-view-filter-display-element-' . $filter->data['element_id'];
		}

		return $filter_form;
	}

	protected function _getFilterStates( Application $application, Model\Filter $filter ) {
		if ( ( ! $conditions = $filter->getFilterConditions() )
			|| empty( $conditions['add'] )
			|| empty( $conditions['rules'] )
		) {
			return;
		}

		$rules = array();
		foreach ( $conditions['rules'] as $rule ) {
			if ( strpos( $rule['field'], ',' ) ) {
				if ( ! $_rule = explode( ',', $rule['field'] ) ) {
					continue;
				}

				$filter_name = $_rule[0];
				$_name       = $_rule[1];
			} else {
				$filter_name = $rule['field'];
				$_name       = '';
			}

			if ( ( ! $_filter = $application->getModel( 'Filter', 'View' )->bundleName_is( $filter->bundle_name )->name_is( $filter_name )->fetchOne() )
				|| ( ! $ifilter = $application->Field_Filters_impl( $_filter->type, true ) )
				|| ! $ifilter instanceof Field\Filter\IConditionable
				|| ( ! $_field = $_filter->getField() )
				|| ! $ifilter->fieldFilterConditionableInfo( $_field )
			) {
				continue;
			}

			$_rule = $ifilter->fieldFilterConditionableRule( $_field, $filter_name, (array) $filter->data['settings'], $rule['compare'], $rule['value'], $_name );
			if ( $_rule = $application->Filter( 'view_filter_condition_rule', $_rule, array( $_field, $filter->data['settings'], $rule['compare'], $rule['value'], $_name ) ) ) {
				if ( ! is_array( $_rule ) ) {
					continue;
				}

				if ( ! isset( $_rule['target'] ) ) {
					$selector        = $filter_name;
					$_rule['target'] = '[name^="' . $selector . '"]';
				}
				$rules[] = $_rule;
			}
		}
		if ( empty( $rules ) ) {
			return;
		}

		$action = $conditions['action']['name'] === 'hide' ? 'invisible_disable' : 'visible_enable';
		if ( $conditions['action']['match'] === 'any' ) {
			$action .= '_or';
		}
		return array( $action => $rules );
	}
}
