<?php
namespace SabaiApps\Directories\Component\Field\Type;

use SabaiApps\Directories\Application;
use SabaiApps\Directories\Component\Entity\Model\Bundle;
use SabaiApps\Directories\Component\Entity\Type\IEntity;
use SabaiApps\Directories\Component\Field\IField;
use SabaiApps\Directories\Component\Field\Query;
use SabaiApps\Directories\Exception\InvalidArgumentException;

class ListType extends AbstractType implements IQueryable, IConditionable, IHumanReadable, ILabellable, ISchemable, IWpRestApi {

	use ChoiceTrait;

	protected $_optionValueRegex = '/^[a-z0-9]([a-z0-9_]*[a-z0-9])?$/';

	protected function _fieldTypeInfo() {
		return array(
			'label'            => __( 'Choice (fixed)', 'directories-pro' ),
			'default_widget'   => 'checkboxes',
			'default_settings' => array(
				'options' => null,
			),
			'icon'             => 'fas fa-tasks',
			'name_max_length'  => 63 - strlen( $this->_application->getDB()->getResourcePrefix() . 'drts_entity_field_' ), // table name max length for MySQL is 64, PostgresQL 63
		);
	}

	public function fieldTypeSettingsForm( $fieldType, Bundle $bundle, array $settings, array $parents = array(), array $rootParents = array() ) {
		return array(
			'options' => array(
				'#type'                   => 'options',
				'#title'                  => __( 'Options', 'directories-pro' ),
				'#description'            => '<span class="' . DRTS_BS_PREFIX . 'text-danger">' . $this->_application->H( __( 'Options may not be added or removed later. Option values may also not be modified later.', 'directories-pro' ) ) . '</span>',
				'#description_no_escape'  => true,
				'#default_value'          => $settings['options'],
				'#multiple'               => true,
				'#default_unchecked'      => true,
				'#value_max_length'       => 50,
				'#value_regex'            => $this->_optionValueRegex,
				'#slugify_value'          => true,
				'#disable_add'            => is_object( $fieldType ),
				'#disable_remove'         => is_object( $fieldType ),
				'#options_value_disabled' => is_object( $fieldType ) && ! empty( $settings['options']['options'] ) ? array_keys( $settings['options']['options'] ) : null,
				'#required'               => true,
			),
		);
	}

	public function fieldTypeSchemaType( $type, $name ) {
		return $name;
	}

	public function fieldTypeSchema( array $settings = array() ) {
		if ( empty( $settings['options']['options'] ) ) {
			throw new InvalidArgumentException( 'Options may not be empty.' );
		}

		$columns = $indexes = array();
		ksort( $settings['options']['options'] );
		foreach ( array_keys( $settings['options']['options'] ) as $key ) {
			if ( ! preg_match( $this->_optionValueRegex, $key ) ) {
				continue;
			}

			$key             = 'v_' . $key;
			$columns[ $key ] = array(
				'type'     => Application::COLUMN_BOOLEAN,
				'unsigned' => true,
				'notnull'  => true,
				'was'      => $key,
				'default'  => false,
			);
			$indexes[ $key ] = array(
				'fields' => array( $key => array( 'sorting' => 'ascending' ) ),
				'was'    => $key,
			);
		}
		if ( empty( $columns ) ) {
			throw new InvalidArgumentException( 'Options may not be empty.' );
		}

		return array(
			'columns' => $columns,
			'indexes' => $indexes,
		);
	}

	public function fieldTypeOnSaveValues( IField $field, array $values, ?array $currentValues = null, array &$extraArgs = array() ) {
		if ( empty( $values ) ) {
			return array( false ); // delete
		}

		$new_value = array();
		foreach ( $values as $value ) {
			$new_value[ 'v_' . $value ] = 1;
		}
		return array( $new_value );
	}

	public function fieldTypeOnLoadValues( IField $field, array &$values, IEntity $entity, array $allValues ) {
		$new_values = array();
		foreach ( array_keys( $values ) as $i ) {
			foreach ( array_keys( $values[ $i ] ) as $column ) {
				if ( empty( $values[ $i ][ $column ] ) ) {
					continue;
				}

				$new_values[] = substr( $column, strlen( 'v_' ) );
			}
		}
		$values = $new_values;
	}

	public function fieldQueryableQuery( Query $query, $fieldName, $paramStr, Bundle $bundle ) {
		if ( $params = $this->_queryableParams( $paramStr ) ) {
			if ( false !== $key = array_search( '_current_', $params ) ) {
				unset( $params[ $key ] );
				if ( ( $entity = $this->_getCurrentEntity() )
					&& ( $values = $entity->getFieldValue( $fieldName ) )
				) {
					foreach ( $values as $value ) {
						$params[] = $value;
					}
				}
			}
			$query->startCriteriaGroup( 'OR' );
			foreach ( $params as $_value ) {
				$query->fieldIs( $fieldName, true, 'v_' . $_value );
			}
			$query->finishCriteriaGroup();
		}
	}

	public function fieldConditionableMatch( IField $field, array $rule, array $values, IEntity $entity ) {
		switch ( $rule['type'] ) {
			case 'value':
			case '!value':
			case 'one':
				if ( empty( $values ) ) {
					return $rule['type'] === '!value';
				}

				foreach ( (array) $rule['value'] as $rule_value ) {
					foreach ( $values as $input ) {
						if ( is_array( $input ) ) {
							// Row from database
							$matched = ! empty( $input[ $rule_value ] );
						} else {
							$matched = $input == $rule_value;
						}
						if ( $matched ) {
							if ( $rule['type'] === '!value' ) {
								return false;
							}
							if ( $rule['type'] === 'one' ) {
								return true;
							}
							continue 2;
						}
					}
					// One of rules did not match
					if ( $rule['type'] === 'value' ) {
						return false;
					}
				}
				// All rules matched or did not match.
				return $rule['type'] !== 'one' ? true : false;
			case 'empty':
				return empty( $values ) === $rule['value'];
			case 'filled':
				return ! empty( $values ) === $rule['value'];
			default:
				return false;
		}
	}
}
