<?php
namespace SabaiApps\Directories\Component\Display\FormField;

use SabaiApps\Directories\Application;
use SabaiApps\Directories\Component\Form;
use SabaiApps\Directories\Exception;

class ElementsFormField extends Form\Field\AbstractField {

	protected $_fields = array();
	protected static $_elementTypes;

	public function formFieldInit( $name, array &$data, Form\Form $form ) {
		if ( empty( $data['#display'] )
			|| empty( $data['#display']['id'] )
		) {
			throw new Exception\RuntimeException( 'Invalid display.' );
		}

		if ( empty( $data['#display']['bundle_name'] )
			|| ( ! $bundle = $this->_application->Entity_Bundle( $data['#display']['bundle_name'] ) )
		) {
			throw new Exception\RuntimeException( 'Invalid bundle.' );
		}
		if ( ! isset( self::$_elementTypes ) ) {
			self::$_elementTypes = $this->_application->Display_Elements_types( $bundle, $data['#display']['type'] );
		}

		$data['#id'] = $form->getFieldId( $name );

		$this->_fields[ $data['#id'] ] = array(
			'display_id' => $data['#display']['id'],
			'name'       => $name,
		);

		if ( ! isset( $form->settings['#pre_render'][ __CLASS__ ] ) ) {
			$form->settings['#pre_render'][ __CLASS__ ] = array( array( $this, 'preRenderCallback' ), array( $bundle, ! empty( $data['#configure_inline'] ) ) );
		}
	}

	public function formFieldRender( array &$data, Form\Form $form ) {
		$html = array(
			'<div class="drts-display-display" data-display-id="' . $this->_application->H( $data['#display']['id'] ) . '" style="position:relative;">',
		);
		if ( $display_links = $this->_application->Filter( 'display_elements_links', array(), array( $data['#display'] ) ) ) {
			$html[] = '<div class="drts-display-elements-links">' . $this->_application->System_Button_dropdownLinks(
				array(
					'' => $this->_application->LinkTo(
						'',
						'#',
						array(
							'active' => true,
							'icon'   => 'fas fa-wrench',
						),
						array( 'class' => DRTS_BS_PREFIX . 'btn-secondary' )
					),
				) + $display_links,
				array( 'right' => true )
			) . '</div>';
		}
		if ( ! empty( $data['#display']['elements'] ) ) {
			$html[] = '<div class="drts-display-element-wrapper" data-container-display-id="' . $this->_application->H( $data['#display']['id'] ) . '">';
			$name   = $this->_application->H( $data['#name'] );
			foreach ( $data['#display']['elements'] as $element ) {
				$html[] = self::getElementHtml( $this->_application, $name, $element, $data['#display']['bundle_name'], ! empty( $data['#display']['locked'] ) );
			}
			$html[] = '</div>';
		} else {
			$html[] = '<div class="drts-display-element-wrapper" data-container-display-id="' . $this->_application->H( $data['#display']['id'] ) . '"></div>';
		}
		$html[] = '<div id="drts-display-element-settings-inline-0" class="drts-display-element-settings-inline"></div>';
		$html[] = '<div class="drts-display-control">
            <button type="button" disabled class="drts-display-add-element drts-display-add-element-main ' . DRTS_BS_PREFIX . 'btn ' . DRTS_BS_PREFIX . 'btn-success" rel="sabaitooltip" title="' . $this->_application->H( __( 'Add Element', 'directories-pro' ) ) . '"><i class="fas fa-plus"></i></button>
        </div>';
		$html[] = '</div>';
		$this->_render( implode( PHP_EOL, $html ), $data, $form );
	}

	public static function getElementHtml( Application $application, $name, array $element, $bundleName, $locked ) {
		ob_start();
		include __DIR__ . '/element.php';
		return ob_get_clean();
	}

	public static function getElementDataArray( Application $application, $bundleName, array $element ) {
		return $application->Display_AdminElement_getDataArray(
			$bundleName,
			$element['_element_id'],
			$element['name'],
			self::$_elementTypes[ $element['type'] ],
			$element['label'],
			$element['title'],
			(array) $element['info'],
			isset( $element['css'] ) ? (array) $element['css'] : array(),
			isset( $element['cache'] ) ? (array) $element['cache'] : array()
		);
	}

	public function formFieldSubmit( &$value, array &$data, Form\Form $form ) {
		if ( ! $display = $this->_application->getModel( 'Display', 'Display' )->fetchById( $data['#display']['id'] ) ) {
			throw new Exception\RuntimeException( 'Invalid display.' );
		}

		$elements                               = $value;
		$value                                  = null;
		$current_display_id                     = $display->id;
		$display_bundles[ $current_display_id ] = $display->bundle_name;

		$current_elements = $updated_elements = array( $current_display_id => array() );
		foreach ( $display->Elements as $element ) {
			$current_elements[ $current_display_id ][ $element->id ] = $element;
		}

		if ( ! empty( $elements ) ) {
			$current_parent_id = array( $current_display_id => 0 );
			$prev_element_id   = null;
			$weight            = $parent_ids = $alt_display_ids = array();
			foreach ( $elements as $element_id ) {
				if ( $element_id === '__CHILDREN_START__' ) {
					$parent_ids[ $current_display_id ][]      = $current_parent_id[ $current_display_id ];
					$current_parent_id[ $current_display_id ] = $prev_element_id;
					continue;
				}

				if ( $element_id === '__CHILDREN_END__' ) {
					$current_parent_id[ $current_display_id ] = array_pop( $parent_ids[ $current_display_id ] );
					continue;
				}

				if ( strpos( $element_id, '__FIELDSET_START__' ) === 0 ) {
					$alt_display_id = substr( $element_id, strlen( '__FIELDSET_START__' ) );
					if ( ! $alt_display = $this->_application->getModel( 'Display', 'Display' )->fetchById( $alt_display_id ) ) {
						throw new Exception\RuntimeException( 'Failed fetching display for fieldset (ID: ' . $alt_display_id . ').' );
					}
					foreach ( $alt_display->Elements as $alt_display_element ) {
						$current_elements[ $alt_display_id ][ $alt_display_element->id ] = $alt_display_element;
					}
					$display_bundles[ $alt_display_id ]       = $alt_display->bundle_name;
					$alt_display_ids[]                        = $current_display_id;
					$current_display_id                       = $alt_display_id;
					$current_parent_id[ $current_display_id ] = 0;
					continue;
				}

				if ( $element_id === '__FIELDSET_END__' ) {
					$current_display_id = array_pop( $alt_display_ids );
					unset( $current_parent_id[ $current_display_id ] );
					continue;
				}

				if ( ! isset( $current_elements[ $current_display_id ][ $element_id ] ) ) {
					continue;
				}

				$element = $updated_elements[ $current_display_id ][ $element_id ] = $current_elements[ $current_display_id ][ $element_id ];

				if ( ! isset( $weight[ $current_display_id ] ) ) {
					$weight[ $current_display_id ] = 0;
				}
				$element->weight = ++$weight[ $current_display_id ];
				if ( isset( $current_parent_id[ $current_display_id ] ) ) {
					$element->parent_id = $current_parent_id[ $current_display_id ];
				}
				if ( $current_display_id ) {
					$element->display_id = $current_display_id;
				}

				$prev_element_id = $element_id;
			}
		}

		// Remove elements
		$elements_removed = array();
		foreach ( array_keys( $current_elements ) as $display_id ) {
			foreach ( empty( $updated_elements[ $display_id ] ) ? $current_elements[ $display_id ] : array_diff_key( $current_elements[ $display_id ], $updated_elements[ $display_id ] ) as $current_element ) {
				if ( $current_element->system ) {
					continue;
				}

				$elements_removed[ $display_id ][ $current_element->id ] = $current_element;
			}
		}
		$this->_application->getModel( null, 'Display' )->commit();

		if ( ! empty( $elements_removed )
			|| ! empty( $updated_elements )
		) {
			foreach ( array_keys( $elements_removed ) as $display_id ) {
				if ( ! $bundle = $this->_application->Entity_Bundle( $display_bundles[ $display_id ] ) ) {
					continue;
				}

				foreach ( $elements_removed[ $display_id ] as $element ) {
					try {
						$this->_application->Display_AdminElement_delete( $bundle, $element->id );
					} catch ( Exception\IException $e ) {
						$this->_application->logError( $e );
					}
				}

				// Clear display elements cache
				$this->_application->getPlatform()->deleteCache( 'display_elements_' . $bundle->name );
			}
			foreach ( array_keys( $updated_elements ) as $display_id ) {
				if ( ! $bundle = $this->_application->Entity_Bundle( $display_bundles[ $display_id ] ) ) {
					continue;
				}

				foreach ( $updated_elements[ $display_id ] as $element ) {
					try {
						$this->_application->Display_Elements_impl( $bundle, $element->name )->displayElementOnPositioned( $bundle, (array) @$element->data['settings'], $element->weight );
					} catch ( Exception\IException $e ) {
						$this->_application->logError( $e );
					}
				}

				// Clear display elements cache
				$this->_application->getPlatform()->deleteCache( 'display_elements_' . $bundle->name );
			}
			// Clear display elements cache
			$data['#clear_display_cache'] = true;
		}

		if ( ! isset( $data['#clear_display_cache'] )
			|| false !== $data['#clear_display_cache']
		) {
			// Clear display and elements cache
			foreach ( $display_bundles as $display_bundle ) {
				$this->_application->Display_Display_clearCache( $display_bundle, $display->type, $display->name );
			}
		}

		// Clear rendered display cache
		$this->_application->Display_Render_clearDisplayCache();

		$this->_application->Action( 'display_admin_display_saved', array( $display, $updated_elements, $elements_removed, $display_bundles ) );
	}

	public function preRenderCallback( Form\Form $form, $bundle, $configureInline ) {
		$admin_path = $this->_application->Entity_BundleTypeInfo_adminPath( $bundle );
		$options    = array(
			'addElementTitle'  => __( 'Add Element', 'directories-pro' ),
			'editElementTitle' => __( 'Edit Element', 'directories-pro' ),
			'deleteConfirm'    => __( 'Are you sure?', 'directories-pro' ),
			'elementTypes'     => self::$_elementTypes,
			'addDisplayUrl'    => (string) $this->_application->Url( $admin_path . '/displays/add_display', array(), '', '&' ),
			'deleteDisplayUrl' => (string) $this->_application->Url( $admin_path . '/displays/delete_display', array(), '', '&' ),
			'saveChangesAlert' => __( 'Please save changes first!', 'directories-pro' ),
		);
		$js         = array(
			sprintf(
				'let adminDisplays = new DRTS.Display.adminDisplays("#%s", %s); ',
				$form->settings['#id'],
				$this->_application->System_Util_jsonEncode( $options )
			),
		);
		foreach ( $this->_fields as $id => $data ) {
			$_options = array(
				'name'            => $data['name'],
				'listElementsUrl' => (string) $this->_application->Url( $admin_path . '/list_display_elements', array( 'display_id' => $data['display_id'] ), '', '&' ),
				'addElementUrl'   => (string) $this->_application->Url( $admin_path . '/add_display_element', array( 'display_id' => $data['display_id'] ), '', '&' ),
				'editElementUrl'  => (string) $this->_application->Url( $admin_path . '/edit_display_element', array( 'display_id' => $data['display_id'] ), '', '&' ),
				'configureInline' => $configureInline,
			);
			$js[]     = sprintf( 'adminDisplays.addDisplay("#%s", %s);', $id, $this->_application->System_Util_jsonEncode( $_options ) );
		}
		$form->settings['#js_ready'][] = implode( PHP_EOL, $js );

		$this->_application->getPlatform()->loadJqueryUiJs( array( 'sortable', 'draggable', 'effects-highlight' ) )
			->addJsFile( 'display-admin-display.min.js', 'drts-display-admin-display', array( 'drts', 'jquery-ui-sortable' ) )
			->addCssFile( 'display-admin-display.min.css', 'drts-display-admin-display', array( 'drts' ) );
		if ( $this->_application->getPlatform()->isRtl() ) {
			$this->_application->getPlatform()->addCssFile( 'display-admin-display-rtl.min.css', 'drts-display-admin-display-rtl', array( 'drts-display-admin-display' ) );
		}
		$this->_application->Form_Scripts();
	}
}
