<?php
namespace SabaiApps\Directories\Component\Faker\SystemTool;

use SabaiApps\Directories\Component\System\Tool\AbstractTool;
use SabaiApps\Directories\Component\Form\Form;
use SabaiApps\Directories\Context;
use SabaiApps\Directories\Exception;
use SabaiApps\Directories\Component\Entity\Model\Bundle;
use SabaiApps\Directories\Component\Entity\Type\IEntity;

class GenerateSystemTool extends AbstractTool {

	protected function _systemToolInfo() {
		return array(
			'label'       => _x( 'Generate demo content', 'system tool name', 'directories-pro' ),
			'description' => '',
			'weight'      => 60,
			'btn_label'   => _x( 'Generate', 'generate content', 'directories-pro' ),
			'steps'       => array(
				'select_mode',
				'select_fields',
				'generator_settings',
			),
			'hidden'      => true,
		);
	}

	protected function _getBundle( Context $context ) {
		return isset( $context->bundle ) && ( $bundle = $this->_application->Entity_Bundle( $context->bundle ) ) ? $bundle : false;
	}

	public function systemToolSettingsForm( $step, array $values, array $parents = array(), ?Context $context = null ) {
		if ( ! $bundle = $this->_getBundle( $context ) ) {
			return false;
		}

		switch ( $step ) {
			case 'select_mode':
				// If there are no existing entities, skip to generating new items
				if ( ! $this->_application->Entity_Query( $bundle->entitytype_name, $bundle->name )->count() ) {
					return array();
				}

				return array(
					'mode' => array(
						'#title'               => __( 'Generation mode', 'directories-pro' ),
						'#type'                => 'radios',
						'#options'             => array(
							'new'    => __( 'Generate new items', 'directories-pro' ),
							'update' => __( 'Generate field values for existing items', 'directories-pro' ),
						),
						'#options_description' => array(
							'new'    => __( 'Create new items with field values generated by the selected generators.', 'directories-pro' ),
							'update' => __( 'Update field values for existing items. This will overwrite existing values unless otherwise specified by each field generator.', 'directories-pro' ),
						),
						'#default_value'       => 'new',
						'#required'            => true,
						'#horizontal'          => true,
					),
				);
			case 'select_fields':
				$options    = $options_disabled = array();
				$mode       = $values['select_mode']['mode'] ?? 'new';
				$generators = $this->_application->Faker_Generators( true );
				$bundle     = $this->_getBundle( $context );
				$fields     = $this->_application->Entity_Field( $bundle->name );
				foreach ( $fields as $field_name => $field ) {
					if ( empty( $generators[ $field->getFieldType() ] ) ) {
						continue;
					}

					foreach ( $generators[ $field->getFieldType() ] as $generator_name ) {
						if ( ( $generator = $this->_application->Faker_Generators_impl( $generator_name, true ) )
							&& $generator->fakerGeneratorSupports( $bundle, $field )
						) {
							$options[ $field_name ]['field'] = $this->_application->Faker_Generators_fieldLabel( $field );
							if ( $mode === 'new'
								&& $field->isPropertyField()
							) {
								$options_disabled[ $field_name ] = $field_name;
							}
							break;
						}
					}
				}

				return array(
					'#header' => array(
						array(
							'level'   => 'info',
							'message' => sprintf( __( 'Select the fields to generate for %s.', 'directories-pro' ), $bundle->getLabel() ),
						),
					),
					'fields'  => array(
						'#type'             => 'tableselect',
						'#header'           => array(
							'field' => __( 'Field name', 'directories-pro' ),
						),
						'#multiple'         => true,
						'#js_select'        => true,
						'#options'          => $options,
						'#options_disabled' => $options_disabled,
						'#default_value'    => $mode === 'new' ? array_keys( $options ) : null,
						'#required'         => empty( $options_disabled ),
						'#element_validate' => array(
							function ( Form $form, &$value, $element ) {
									$value = array_merge( $element['#options_disabled'], (array) $value );
							},
						),
					),
				);

			case 'generator_settings':
				$form            = array( 'settings' => array() );
				$mode            = $values['select_mode']['mode'] ?? 'new';
				$bundle          = $this->_getBundle( $context );
				$fields          = $this->_application->Entity_Field( $bundle->name );
				$selected_fields = (array) ( $values['select_fields']['fields'] ?? array() );
				foreach ( $selected_fields as $field_name ) {
					if ( ! isset( $fields[ $field_name ] ) ) {
						continue;
					}

					$field = @$fields[ $field_name ];
					if ( ! $settings_form = $this->_application->Faker_Generators_settingsForm( $field, $mode, array_merge( $parents, array( 'settings', $field_name ) ) ) ) {
						continue;
					}

					$form['settings'][ $field_name ] = $settings_form;
				}

				if ( empty( $form['settings'] ) ) {
					return array();
				}

				$form['settings']['#tree'] = true;
				$form['#header'][]         = array(
					'level'   => 'info',
					'message' => __( 'Please configure additional options for each field.', 'directories-pro' ),
				);

				return $form;

			case 'run':
				$bundle = $this->_getBundle( $context );
				$mode   = $values['select_mode']['mode'] ?? 'new';
				if ( $mode === 'update' ) {
					$form = array(
						'query' => array(
							'#title'            => __( 'Filter items by field values', 'directories-pro' ),
							'#tree'             => true,
							'#element_validate' => array(
								function ( Form $form, &$value, $element ) {
									if ( empty( $value['fields'] ) ) {
										return;
									}

									$queries = array();
									foreach ( array_filter( $value['fields'] ) as $query ) {
										if ( ! strlen( $query['field'] )
											|| ! strlen( trim( $query['query'] ) )
										) {
											continue;
										}

										$queries[ $query['field'] ] = $query['query'];
									}
									$value['fields'] = $queries;
								},
							),
							'fields'            => array(
								array(
									'#type'           => 'field_query',
									'#fields'         => $this->_application->Entity_Field( $bundle ),
									'#default_value'  => null,
									'#query_in_admin' => true,
								),
							),
							'_add'              => array(
								'#type' => 'addmore',
							),
							'#horizontal'       => true,
							'#description'      => __( 'Optionally filter existing items based on specific field values before generating dummy content.', 'directories-pro' ),
						),
						'limit' => array(
							'#type'          => 'number',
							'#title'         => __( 'Limit to X items (0 for all items)', 'directories-pro' ),
							'#default_value' => 0,
							'#min_value'     => 0,
							'#integer'       => true,
							'#horizontal'    => true,
						),
					);
				} else {
					$form = array(
						'num' => array(
							'#type'          => 'number',
							'#title'         => __( 'Number of items to generate', 'directories-pro' ),
							'#default_value' => 10,
							'#min_value'     => 1,
							'#integer'       => true,
							'#horizontal'    => true,
						),
					);
				}
				$form += array(
					'limit_request' => array(
						'#type'          => 'slider',
						'#title'         => __( 'Number of items to process per request', 'directories-pro' ),
						'#description'   => __( 'Adjust this setting if you are experiencing timeout errors.', 'directories-pro' ),
						'#default_value' => empty( $bundle->info['is_taxonomy'] ) ? 10 : 20,
						'#min_value'     => 1,
						'#max_value'     => 50,
						'#integer'       => true,
						'#horizontal'    => true,
					),
				);
				return $form;
			default:
		}
	}

	public function systemToolInit( array $settings, array &$storage, array &$logs, ?Context $context = null ) {
		if ( $settings['select_mode']['mode'] === 'update' ) {
			$bundle       = $this->_getBundle( $context );
			$limit        = (int) $settings['run']['limit'];
			$query_fields = empty( $settings['run']['query']['fields'] ) ? array() : $settings['run']['query']['fields'];
			$count        = $this->_getQuery( $bundle, $query_fields )->count();
			return array( 'default' => empty( $limit ) || $count < $limit ? $count : $limit );
		}
		return array( 'default' => (int) $settings['run']['num'] );
	}

	protected function _getQuery( Bundle $bundle, array $fields ) {
		$query = $this->_application->Entity_Query( $bundle->entitytype_name, $bundle->name, $fields );
		return $this->_application->Filter( 'faker_generate_query', $query, array( $bundle ) );
	}

	public function systemToolRunTask( $task, array $settings, $iteration, $total, array &$storage, array &$logs, ?Context $context = null ) {
		$selected_fields    = $settings['select_fields']['fields'];
		$generator_settings = (array) ( $settings['generator_settings']['settings'] ?? array() );
		$bundle             = $this->_getBundle( $context );
		$fields             = $this->_application->Entity_Field( $bundle->name );

		if ( $settings['select_mode']['mode'] === 'update' ) {
			$paginator = $this->_getQuery( $bundle, empty( $settings['run']['query']['fields'] ) ? array() : $settings['run']['query']['fields'] )
				->sortById()
				->paginate( (int) $settings['run']['limit_request'] )
				->setCurrentPage( $iteration );
			$entities  = $paginator->getElements();
			foreach ( $entities as $entity ) {
				try {
					$values = $this->_generateFieldValues( $selected_fields, $fields, $generator_settings, $storage, $logs, $entity );
					if ( empty( $values ) ) {
						continue;
					}

					$this->_application->Entity_Save( $entity, $values );

					$logs['success'][] = sprintf(
						'%s updated: %s (ID: %d)',
						$bundle->getGroupLabel() . ' - ' . $bundle->getLabel( 'singular' ),
						$this->_application->Entity_Title( $entity, true ),
						$entity->getId()
					);
				} catch ( Exception\IException $e ) {
					$logs['error'][] = $e->getMessage();
				}
			}
			$num_processed = $paginator->getElementLimit();
		} else {
			$num_processed = 0;
			$limit         = (int) $settings['run']['limit_request'];
			$num           = (int) $settings['run']['num'];
			if ( $num - ( $iteration - 1 ) * $limit < $limit ) {
				$limit = $num - ( $iteration - 1 ) * $limit;
				if ( $limit <= 0 ) {
					$limit = $settings['run']['num'];
				}
			}
			for ( $i = 0; $i < $limit; $i++ ) {
				try {
					$values = $this->_generateFieldValues( $selected_fields, $fields, $generator_settings, $storage, $logs );
					if ( empty( $values['post_author'] ) ) {
						$values['post_author'] = 0;
					}
					$entity = $this->_application->Entity_Save( $bundle, $values );
					++$num_processed;

					$logs['success'][] = sprintf(
						'%s generated: %s (ID: %d)',
						$bundle->getGroupLabel() . ' - ' . $bundle->getLabel( 'singular' ),
						$this->_application->Entity_Title( $entity, true ),
						$entity->getId()
					);

					// Notify
					$this->_application->Action( 'faker_generate_entity', array( $bundle, $entity, $values, $generator_settings, $storage, $logs ) );
				} catch ( Exception\IException $e ) {
					$logs['error'][] = $e->getMessage();
				}
			}
		}

		return $num_processed;
	}

	protected function _generateFieldValues( array $fieldsSelected, array $fields, array $settings, array &$storage, array &$logs, ?IEntity $entity = null ) {
		$values = array();
		foreach ( array_keys( $fieldsSelected ) as $j ) {
			$field_name = $fieldsSelected[ $j ];
			if ( ! isset( $fields[ $field_name ] )
				|| ( ! $_settings = $settings[ $field_name ] ?? null )
			) {
				continue;
			}

			$value = $this->_application->Faker_Generators_generate( $fields[ $field_name ], $_settings, $storage, $entity );
			if ( $value === null ) {
				continue;
			}

			if ( $value === false ) {
				unset( $fields[ $j ] ); // an error occurred with this generator, so skip this field for subsequent items
				continue;
			}

			$values[ $field_name ] = $value;
		}
		return $values;
	}
}
