<?php
namespace SabaiApps\Directories\Component\Directory;

use SabaiApps\Directories\Component\AbstractComponent;
use SabaiApps\Directories\Component\System;
use SabaiApps\Directories\Component\Entity;
use SabaiApps\Directories\Exception;
use SabaiApps\Directories\Application;
use SabaiApps\Directories\Context;

class DirectoryComponent extends AbstractComponent implements
	ITypes,
	System\ISlugs,
	Entity\IBundleTypes,
	System\IAdminRouter,
	System\IWidgets,
	System\ITools {

	const VERSION = '2.0.6', PACKAGE = 'directories-pro';

	public static function description() {
		return 'Adds features to build a general directory of listings with category and tag taxonomies.';
	}

	public function onCoreComponentsLoaded() {
		$this->_application->setHelper(
			'Directory_Directory',
			function ( Application $application, $directoryName ) {
				return $application->getModel( 'Directory', 'Directory' )->fetchById( $directoryName );
			}
		);
	}

	public function systemAdminRoutes() {
		return array(
			'/directories'                                 => array(
				'controller'     => 'Directories',
				'title_callback' => true,
				'callback_path'  => 'directories',
				'type'           => Application::ROUTE_TAB,
				'weight'         => 9,
			),
			'/directories/:directory_name'                 => array(
				'controller'      => 'EditDirectory',
				'title_callback'  => true,
				'access_callback' => true,
				'callback_path'   => 'edit_directory',
				'type'            => Application::ROUTE_TAB,
			),
			'/directories/:directory_name/content_types'   => array(
				'controller'     => 'ContentTypes',
				'title_callback' => true,
				'callback_path'  => 'content_types',
				'type'           => Application::ROUTE_TAB,
			),
			'/directories/:directory_name/delete'          => array(
				'controller'     => 'DeleteDirectory',
				'title_callback' => true,
				'callback_path'  => 'delete_directory',
			),
			'/directories/:directory_name/export_settings' => array(
				'controller'     => 'ExportDirectorySettings',
				'title_callback' => true,
				'callback_path'  => 'export_directory_settings',
			),
			'/directories/add'                             => array(
				'controller'      => 'AddDirectory',
				'access_callback' => true,
				'title_callback'  => true,
				'callback_path'   => 'add_directory',
				'type'            => Application::ROUTE_MENU,
			),
		);
	}

	public function systemOnAccessAdminRoute( Context $context, $path, $accessType, array &$route ) {
		switch ( $path ) {
			case 'edit_directory':
				if ( $accessType === Application::ROUTE_ACCESS_LINK ) {
					if ( ( ! $directory_name = $context->getRequest()->asStr( 'directory_name' ) )
						|| ( ! $directory = $this->getModel( 'Directory' )->fetchById( $directory_name ) )
					) {
						return false;
					}

					if ( ! $this->_application->HasPermission( 'directory_admin_directory_' . $directory_name ) ) {
						$context->setForbiddenError();
						return false;
					}

					$context->clearMenus();
					// Add directory admin menus
					$menus = array(
						'visit' => array(
							'title' => __( 'Visit Directory', 'directories-pro' ),
							'url'   => $this->_application->MainUrl( '/' . $this->getSlug( $directory_name ) ),
						),
					);
					if ( $this->_application->getUser()->isAdministrator() ) {
						$menus['export_settings'] = array(
							'title' => $title = __( 'Export Settings', 'directories-pro' ),
							'url'   => '/directories/' . $directory_name . '/export_settings',
							'data'  => array(
								'link_options' => array(
									'container' => 'modal',
									'modalSize' => 'xl',
								),
								'link_attr'    => array( 'data-modal-title' => $title . ' - ' . $directory->getLabel() ),
							),
							'page'  => true,
						);
					}
					foreach ( $this->_application->Filter( 'directory_admin_directory_menus', $menus, array( $directory ) ) as $menu ) {
						$context->addMenu(
							array(
								'title' => $menu['title'],
								'url'   => $menu['url'],
								'data'  => isset( $menu['data'] ) ? $menu['data'] : null,
							),
							! isset( $menu['page'] ) || $menu['page']
						);
					}

					$context->directory = $directory;
				}
				return true;
			case 'add_directory':
				if ( $accessType === Application::ROUTE_ACCESS_LINK ) {
					if ( ! $this->_application->getUser()->isAdministrator() ) {
						return false;
					}

					$route['data'] = array(
						'link_options' => array(
							'container' => 'modal',
							'modalSize' => 'xl',
							'cache'     => 'drts-directory-add-directory',
							'icon'      => 'fas fa-plus',
						),
						'link_attr'    => array(
							'class' => DRTS_BS_PREFIX . 'btn ' . DRTS_BS_PREFIX . 'btn-success',
						),
					);
				}
				return true;
			default:
				if ( $accessType === Application::ROUTE_ACCESS_LINK ) {
					return $this->_application->getUser()->isAdministrator();
				}
				return true;
		}
	}

	public function systemAdminRouteTitle( Context $context, $path, $titleType, array $route ) {
		switch ( $path ) {
			case 'directories':
				return $titleType === Application::ROUTE_TITLE_TAB ? __( 'All Directories', 'directories-pro' ) : 'Directories';
			case 'edit_directory':
				return $titleType === Application::ROUTE_TITLE_TAB ? __( 'Settings', 'directories-pro' ) : $context->directory->getLabel();
			case 'delete_directory':
				return __( 'Delete Directory', 'directories-pro' );
			case 'export_directory_settings':
				return __( 'Export Settings', 'directories-pro' );
			case 'add_directory':
				return __( 'Add Directory', 'directories-pro' );
			case 'content_types':
				return __( 'Content Types', 'directories-pro' );
			case 'settings':
				return __( 'Settings', 'directories-pro' );
			case 'system':
				return __( 'System', 'directories-pro' );
		}
	}

	public function systemSlugs() {
		$slugs          = array();
		$component_slug = strtolower( $this->_name );
		foreach ( $this->getModel( 'Directory' )->fetch() as $directory ) {
			if ( ! $directory_type = $this->_application->Directory_Types_impl( $directory->type, true ) ) {
				continue;
			}

			$slugs[ $directory->name ] = array(
				'title'        => $directory->getLabel(),
				'admin_title'  => $directory->getLabel(),
				'slug'         => $component_slug . '-' . $directory->name,
				'bundle_group' => $directory->name,
				'wp_shortcode' => array( 'drts-directory-view', array( 'directory' => $directory->name ) ),
				'weight'       => 100,
			);
		}
		return $slugs;
	}

	public function entityGetBundleTypeNames() {
		$ret = array( 'directory_category', 'directory_tag' );
		foreach ( array_keys( $this->_application->Directory_Types() ) as $directory_type_name ) {
			if ( ! $directory_type = $this->_application->Directory_Types_impl( $directory_type_name, true ) ) {
				continue;
			}

			foreach ( $directory_type->directoryInfo( 'content_types' ) as $content_type ) {
				$ret[] = $directory_type_name . '__' . $content_type;
			}
		}

		return $ret;
	}

	public function entityGetBundleType( $name ) {
		switch ( $name ) {
			case 'directory_category':
				return new EntityBundleType\CategoryEntityBundleType( $this->_application, $name );
			case 'directory_tag':
				return new EntityBundleType\TagEntityBundleType( $this->_application, $name );
			default:
				return new EntityBundleType\DirectoryEntityBundleType( $this->_application, $name );
		}
	}

	public function onWordPressPostTypeFilter( &$postType, $bundle ) {
		if ( $directory = $this->getModel( 'Directory' )->fetchById( $bundle->group ) ) {
			$postType['labels']['name']          = $directory->getLabel() . ' - ' . $bundle->getLabel();
			$postType['labels']['singular_name'] = $directory->getLabel() . ' - ' . $bundle->getLabel( 'singular' );
			if ( empty( $bundle->info['parent'] )
				&& empty( $bundle->info['parent_field'] )
			) {
				$postType['labels']['menu_name'] = $directory->getLabel();
				if ( $icon = $directory->getIcon() ) {
					foreach ( explode( ' ', $icon ) as $dashicon ) {
						if ( strpos( $dashicon, 'dashicons-' ) === 0 ) {
							$postType['menu_icon'] = $dashicon;
							break;
						}
					}
				}
			}
		}
	}

	public function onWordPressTaxonomyFilter( &$taxonomy, $bundle ) {
		if ( $directory = $this->getModel( 'Directory' )->fetchById( $bundle->group ) ) {
			$taxonomy['labels']['name']          = $directory->getLabel() . ' - ' . $bundle->getLabel();
			$taxonomy['labels']['singular_name'] = $directory->getLabel() . ' - ' . $bundle->getLabel( 'singular' );
		}
	}

	public function directoryGetTypeNames() {
		return array( 'directory' );
	}

	public function directoryGetType( $name ) {
		switch ( $name ) {
			case 'directory':
				return new Type\DirectoryType( $this->_application, $name );
		}
	}

	public function systemGetWidgetNames() {
		$ret = array();
		foreach ( $this->_application->Entity_Bundles_byType( 'directory__listing' ) as $bundle ) {
			$ret[] = 'directory_posts_' . $bundle->type;
			$ret[] = 'directory_related_posts_' . $bundle->type;
			if ( ! empty( $bundle->info['taxonomies'] ) ) {
				foreach ( array_keys( $bundle->info['taxonomies'] ) as $taxonomy_bundle_type ) {
					$widget_name = 'directory_terms_' . $taxonomy_bundle_type . '___' . $bundle->type;
					if ( ! in_array( $widget_name, $ret )
						&& ( $taxonomy_bundle = $this->_application->Entity_Bundle( $taxonomy_bundle_type, $bundle->group ) )
						&& ! empty( $taxonomy_bundle->info['is_hierarchical'] )
					) {
						$ret[] = $widget_name;
					}
				}
			}
		}

		return array_unique( $ret );
	}

	public function systemGetWidget( $name ) {
		if ( strpos( $name, 'directory_terms_' ) === 0 ) {
			return new SystemWidget\TermsSystemWidget( $this->_application, $name, substr( $name, 16 ) );
		}
		if ( strpos( $name, 'directory_posts_' ) === 0 ) {
			return new SystemWidget\PostsSystemWidget( $this->_application, $name, substr( $name, 16 ) );
		}
		if ( strpos( $name, 'directory_related_posts_' ) === 0 ) {
			return new SystemWidget\RelatedPostsSystemWidget( $this->_application, $name, substr( $name, 24 ) );
		}
	}

	public function onEntityBundleTypesFilter( &$bundleTypes ) {
		foreach ( array_keys( $bundleTypes ) as $bundle_type ) {
			if ( $bundleTypes[ $bundle_type ] !== $this->_name
				|| ! strpos( $bundle_type, '__' )
			) {
				continue;
			}

			$parts = explode( '__', $bundle_type );
			if ( ! $this->_application->Directory_Types_Impl( $parts[0], true, false ) ) {
				// Remove the entity bundle type since the directory type associated is disabled or removed
				unset( $bundleTypes[ $bundle_type ] );
			}
		}
	}

	public function onWordPressAdminEndpointsFilter( &$endpoints ) {
		$endpoints['/directories'] = $this->_application->Filter(
			'wordpress_directory_admin_endpoint',
			array(
				'post_type'            => null,
				'label'                => __( 'All Directories', 'directories-pro' ),
				'label_menu'           => 'Directories',
				'label_post_type_menu' => __( 'Settings', 'directories-pro' ),
				'order'                => 99,
				'capability'           => 'read',
			)
		);
	}

	public function onWordPressPermissionsFilter( &$permissions, $componentName, $group ) {
		if ( $componentName !== 'Directory' ) {
			return;
		}

		$permissions['directory_admin'] = array(
			'title'  => _x( 'Admin', 'permission tab label', 'directories-pro' ),
			'perms'  => array(
				'directory_admin_directory' => array(
					'title'  => _x( 'Admin Directory', 'permission name', 'directories-pro' ),
					'weight' => 1,
				),
			),
			'suffix' => '_' . $group,
		);
	}

	public function getAvailableTaxonomies() {
		return $this->_application->Filter(
			'directory_content_type_available_taxonomies',
			array(
				'directory_category' => array(),
				'directory_tag'      => array(),
			)
		);
	}

	public function onDirectoryContentTypeSettingsFormFilter( &$form, $directoryType, $contentType, $info, $settings, $parents, $submitValues ) {
		if ( ! empty( $info['is_taxonomy'] )
			|| ! empty( $info['parent'] )
			|| ! empty( $info['parent_field'] )
			|| ! in_array( is_object( $directoryType ) ? $directoryType->type : $directoryType, array( 'directory' ) )
		) {
			return;
		}

		$weight     = 5;
		$taxonomies = $this->getAvailableTaxonomies();
		foreach ( $taxonomies as $taxonomy => $taxonomy_settings ) {
			$setting_name = ( isset( $taxonomy_settings['prefix'] ) ? $taxonomy_settings['prefix'] : $taxonomy ) . '_enable';
			if ( empty( $taxonomy_settings['is_custom'] )
				&& empty( $info[ $setting_name ] ) // non-custom taxonomy requires hard-coded info property
			) {
				continue;
			}

			try {
				if ( ! isset( $taxonomy_settings['component'] ) ) {
					$entity_bundle_type_impl = $this->entityGetBundleType( $taxonomy );
				} else {
					$entity_bundle_type_impl = $this->_application->getComponent( $taxonomy_settings['component'] )
						->entityGetBundleType( $taxonomy );
				}
			} catch ( Exception\IException $e ) {
				$this->_application->logError( $e->getMessage() );
				continue;
			}
			$taxonomy_label        = $entity_bundle_type_impl->entityBundleTypeInfo( 'label' );
			$form[ $setting_name ] = array(
				'#type'          => 'checkbox',
				'#title'         => __( 'Enable taxonomy', 'directories-pro' ) . ' - ' . $taxonomy_label,
				'#default_value' => ! empty( $settings[ $setting_name ] ) || is_null( $settings ),
				'#horizontal'    => true,
				'#weight'        => ++$weight,
			);
		}
	}

	public function onDirectoryContentTypeInfoFilter( &$info, $contentType, $settings = null ) {
		$is_valid = empty( $info['is_taxonomy'] ) && empty( $info['parent'] ) && empty( $info['parent_field'] );

		// Check taxnomies are enabled.
		foreach ( $this->getAvailableTaxonomies() as $taxonomy => $taxonomy_settings ) {
			$setting_name = ( isset( $taxonomy_settings['prefix'] ) ? $taxonomy_settings['prefix'] : $taxonomy ) . '_enable';
			if ( ! $is_valid ) {
				unset( $info[ $setting_name ] );
				continue;
			}
			if ( empty( $taxonomy_settings['is_custom'] ) ) {
				if ( ! isset( $info[ $setting_name ] ) ) {
					continue;
				}

				if ( isset( $settings[ $setting_name ] ) && ! $settings[ $setting_name ] ) {
					$info[ $setting_name ] = false;
				}
			} elseif ( isset( $settings[ $setting_name ] ) && $settings[ $setting_name ] ) {
					$info[ $setting_name ] = true;
			}
		}
	}

	public function onEntityBundlesInfoFilter( &$bundles, $group ) {
		foreach ( $this->getAvailableTaxonomies() as $entity_bundle_type => $taxonomy_settings ) {
			foreach ( array_keys( $bundles ) as $bundle_type ) {
				$info         =& $bundles[ $bundle_type ];
				$setting_name = ( isset( $taxonomy_settings['prefix'] ) ? $taxonomy_settings['prefix'] : $entity_bundle_type ) . '_enable';

				if ( empty( $info[ $setting_name ] )
					|| ! empty( $info['is_taxonomy'] )
					|| ! empty( $info['parent'] )
					|| ! empty( $info['parent_field'] )
				) {
					continue;
				}

				try {
					if ( ! isset( $taxonomy_settings['component'] ) ) {
						$entity_bundle_type_impl = $this->entityGetBundleType( $entity_bundle_type );
					} else {
						$entity_bundle_type_impl = $this->_application->getComponent( $taxonomy_settings['component'] )
							->entityGetBundleType( $entity_bundle_type );
					}
					$entity_bundle_type_info = $entity_bundle_type_impl->entityBundleTypeInfo();
				} catch ( Exception\IException $e ) {
					$this->_application->logError( $e->getMessage() );
					continue;
				}

				// Associate bundle
				if ( ! isset( $info['taxonomies'][ $entity_bundle_type ] ) // may already be set if updating or importing
					|| ! is_array( $info['taxonomies'][ $entity_bundle_type ] ) // not an array if updating
				) {
					$info['taxonomies'][ $entity_bundle_type ] = array();
				}
				if ( ! empty( $info[ $entity_bundle_type . '_field' ] ) ) {
					$info['taxonomies'][ $entity_bundle_type ] += $info[ $entity_bundle_type . '_field' ];
				}

				// Add bundle
				if ( ! isset( $bundles[ $entity_bundle_type ] ) ) { // may already set if updating
					$bundles[ $entity_bundle_type ] = array();
				}
				$bundles[ $entity_bundle_type ] += $entity_bundle_type_info;

				continue 2; // there should be only one bundle enabled
			}

			// No bundle enabled found, so make sure the bundle is not assigned
			unset( $bundles[ $entity_bundle_type ] );
		}
	}

	public function onEntityBundleInfoKeysFilter( &$keys ) {
		$keys[] = 'directory_category_enable';
		$keys[] = 'directory_tag_enable';
	}

	public function onWordPressShortcodesFilter( &$shortcodes ) {
		foreach ( $this->_application->Filter( 'directory_shortcodes', array() ) as $name => $path ) {
			$data = array( 'component' => 'Directory' );
			if ( is_array( $path ) ) {
				$data += $path;
			} else {
				$data['path'] = $path;
			}
			$shortcodes[ 'drts-directory-' . $name ] = $data + array( 'cache' => true );
		}
	}

	public function onWordPressDoShortcodeFilter( &$ret, $shortcode, $component ) {
		if ( strpos( $shortcode, 'drts-directory-' ) !== 0 ) {
			return;
		}

		if ( ! isset( $ret['atts']['directory'] ) ) {
			if ( ! $directory = $this->getModel( 'Directory' )->fetchOne() ) {
				throw new Exception\RuntimeException( 'Shortcode [' . $shortcode . ']: No directory found.' );
			}
			$ret['atts']['directory'] = $directory->name;
		}
		if ( strpos( $ret['atts']['directory'], ',' ) ) {
			$directories = array_map( 'trim', explode( ',', $ret['atts']['directory'] ) );
			$paths       = $atts = $options = array();
			foreach ( $this->getModel( 'Directory', 'Directory' )->name_in( $directories )->fetch() as $directory ) {
				if ( ! $this->_application->Directory_Types_impl( $directory->type, true ) ) {
					continue;
				}

				$_ret                      = $ret;
				$_ret['atts']['directory'] = $directory->name;
				$this->_processDirectoryForShortcode( $directory, $_ret, $shortcode );

				$paths[ $directory->name ]   = array(
					'path'  => $_ret['path'],
					'title' => $directory->getLabel(),
					'icon'  => $directory->getIcon(),
				);
				$atts[ $directory->name ]    = $_ret['atts'];
				$options[ $directory->name ] = isset( $_ret['options'] ) ? $_ret['options'] : array();
			}
			if ( empty( $paths ) ) {
				throw new Exception\RuntimeException( 'Shortcode [' . $shortcode . ']: No valid directories found (' . $ret['atts']['directory'] . '). Make sure directories exist.' );
			}

			// Sort paths, atts, and options according to the order of directories specified in the shortcode
			$paths_sorted = $atts_sorted = $options_sorted = array();
			foreach ( $directories as $directory_name ) {
				if ( isset( $paths[ $directory_name ] ) ) {
					$paths_sorted[]   = $paths[ $directory_name ];
					$atts_sorted[]    = $atts[ $directory_name ];
					$options_sorted[] = $options[ $directory_name ];
				}
			}

			$ret['path']    = array_values( $paths_sorted );
			$ret['atts']    = array_values( $atts_sorted );
			$ret['options'] = array_values( $options_sorted );
		} else {
			if ( ! isset( $directory ) ) {
				if ( ! $directory = $this->getModel( 'Directory' )->fetchById( $ret['atts']['directory'] ) ) {
					throw new Exception\RuntimeException( 'Shortcode [' . $shortcode . ']: Directory not found (' . $ret['atts']['directory'] . '). Make sure directory exists.' );
				}
			}
			$this->_processDirectoryForShortcode( $directory, $ret, $shortcode );
		}

		return $ret;
	}

	protected function _processDirectoryForShortcode( $directory, &$ret, $shortcode ) {
		if ( ! isset( $ret['atts']['type'] ) ) {
			// No bundle type specified, so fetch the primary bundle of the directory
			foreach ( $this->_application->Entity_Bundles( null, $directory->name ) as $_bundle ) {
				if ( ! empty( $_bundle->info['is_primary'] ) ) {
					$bundle = $_bundle;
					break;
				}
			}
			if ( ! isset( $bundle ) ) {
				throw new Exception\RuntimeException( 'Shortcode [' . $shortcode . ']: content type not found for directory (' . $directory->name . '). Make sure directory exists.' );
			}
			$ret['atts']['type'] = $bundle->type;
		} elseif ( ! $bundle = $this->_application->Entity_Bundle( $ret['atts']['type'], $directory->name ) ) {
				throw new Exception\RuntimeException( 'Shortcode [' . $shortcode . ']: invalid content type (' . $ret['atts']['type'] . ') for directory (' . $directory->name . '). Make sure directory exists and content type is correct.' );
		}

		$ret['atts']['directory'] = $directory->name;
		$ret['path']              = $bundle->getPath() . $ret['path'];

		// Since $ret is passes as reference, we can't return it here
		$ret = $this->_application->Filter(
			'directory_do_shortcode',
			$ret,
			array( substr( $shortcode, strlen( 'drts-directory-' ) ), $bundle )
		);
	}

	public function onEntityDeleteBundlesCommitted( array $bundles, $deleteContent ) {
		$taxonomy_field_names = array();
		foreach ( $bundles as $bundle ) {
			if ( empty( $bundle->info['is_taxonomy'] ) ) {
				if ( $bundle->type === 'directory__listing' ) {
					// Abort since there is no need to remove taxonomy term fields individually if listing bundle already deleted
					return;
				}
				continue;
			}

			$taxonomy_field_names[ $bundle->group ][] = $bundle->type;
		}
		if ( empty( $taxonomy_field_names ) ) {
			return;
		}

		// Remove taxonomy term fields
		foreach ( array_keys( $taxonomy_field_names ) as $group ) {
			foreach ( $taxonomy_field_names[ $group ] as $field_name ) {
				if ( $field = $this->_application->Entity_Field( 'directory__listing', $field_name, 'Directory', $group ) ) {
					$field->markRemoved();
				}
			}
		}
		$this->_application->getComponent( 'Entity' )->getModel()->commit();
	}

	public function onCorePlatformWordPressInit() {
		if ( $this->_application->getPlatform()->getName() === 'WordPress'
			&& $this->_application->getPlatform()->getThemeSlug() === 'storefront'
		) {
			new WordPressHomePage( $this->_application );
		}
	}

	public function systemGetToolNames() {
		return array( 'directory_export_directory_settings' );
	}

	public function systemGetTool( $name ) {
		if ( $name === 'directory_export_directory_settings' ) {
			return new SystemTool\ExportDirectorySettingsSystemTool( $this->_application, $name );
		}
	}

	public function onWordPressAdminMenuPostTypeIconsFilter( &$icons ) {
		foreach ( $this->getModel( 'Directory' )->fetch() as $directory ) {
			if ( ( $icon = $directory->getIcon() )
				&& strpos( $icon, 'dashicons-' ) === false // skip dashicons since they are handled natively by WP
			) {
				$icons[ $directory->name . '_dir_ltg' ] = $icon;
			}
		}
	}
}
