<?php
/**
 * Admin pages manager class
 *
 * @package Leaves_And_Love\Plugin_Lib
 * @since 1.0.0
 */

namespace Leaves_And_Love\Plugin_Lib\Components;

use Leaves_And_Love\Plugin_Lib\Service;
use Leaves_And_Love\Plugin_Lib\Traits\Container_Service_Trait;
use Leaves_And_Love\Plugin_Lib\Traits\Hook_Service_Trait;
use Leaves_And_Love\Plugin_Lib\Assets;
use Leaves_And_Love\Plugin_Lib\AJAX;
use Leaves_And_Love\Plugin_Lib\Error_Handler;

if ( ! class_exists( 'Leaves_And_Love\Plugin_Lib\Components\Admin_Pages' ) ) :

	/**
	 * Class for the Admin Pages API
	 *
	 * This class manages admin pages.
	 *
	 * @since 1.0.0
	 */
	class Admin_Pages extends Service {
		use Container_Service_Trait, Hook_Service_Trait;

		/**
		 * Added admin pages.
		 *
		 * @since 1.0.0
		 * @var array
		 */
		protected $pages = array();

		/**
		 * Hook suffixes of the added admin pages.
		 *
		 * @since 1.0.0
		 * @var array
		 */
		protected $hook_suffix_map = array();

		/**
		 * Assets service definition.
		 *
		 * @since 1.0.0
		 * @static
		 * @var string
		 */
		protected static $service_assets = Assets::class;

		/**
		 * AJAX service definition.
		 *
		 * @since 1.0.0
		 * @static
		 * @var string
		 */
		protected static $service_ajax = AJAX::class;

		/**
		 * Constructor.
		 *
		 * @since 1.0.0
		 *
		 * @param string $prefix   The prefix for all shortcodes.
		 * @param array  $services {
		 *     Array of service instances.
		 *
		 *     @type Assets        $assets        The Assets API instance.
		 *     @type AJAX          $ajax          The AJAX API instance.
		 *     @type Error_Handler $error_handler The error handler instance.
		 * }
		 */
		public function __construct( $prefix, $services ) {
			$this->set_prefix( $prefix );
			$this->set_services( $services );

			$this->setup_hooks();
		}

		/**
		 * Adds an admin page.
		 *
		 * This method must be called before the 'admin_menu' action.
		 *
		 * @since 1.0.0
		 *
		 * @param string            $slug                 Page slug.
		 * @param string|Admin_Page $class_name           Either the name of the class to handle the
		 *                                                page, or an already instantiated object of
		 *                                                that class.
		 * @param string|null       $parent_slug          Optional. Parent page slug. Default null.
		 * @param int|null          $position             Optional. Page position index. Default null.
		 * @param string            $administration_panel Optional. Either 'site', 'network' or 'user'.
		 *                                                Default 'site'.
		 * @param bool              $skip_menu            Optional. Whether to not add a menu or submenu
		 *                                                item. Default false.
		 * @return bool True on success, false on failure.
		 */
		public function add( $slug, $class_name, $parent_slug = null, $position = null, $administration_panel = 'site', $skip_menu = false ) {
			if ( ! is_subclass_of( $class_name, 'Leaves_And_Love\Plugin_Lib\Components\Admin_Page' ) ) {
				return false;
			}

			if ( ! in_array( $administration_panel, array( 'site', 'network', 'user' ), true ) ) {
				return false;
			}

			if ( $this->exists( $slug, $administration_panel ) ) {
				return false;
			}

			$slug = $this->get_prefix() . $slug;

			if ( is_object( $class_name ) ) {
				$page = $class_name;
				if ( $page->slug !== $slug ) {
					return false;
				}
			} else {
				$page = new $class_name( $slug, $this );
			}

			if ( $parent_slug && isset( $this->pages[ $administration_panel ][ $this->get_prefix() . $parent_slug ] ) ) {
				$parent_slug = $this->get_prefix() . $parent_slug;
			}

			$page->administration_panel = $administration_panel;
			$page->parent_slug          = $parent_slug;
			$page->position             = $position;
			$page->skip_menu            = $skip_menu;

			if ( ! isset( $this->pages[ $administration_panel ] ) ) {
				$this->pages[ $administration_panel ] = array();
			}

			if ( ! isset( $this->hook_suffix_map[ $administration_panel ] ) ) {
				$this->hook_suffix_map[ $administration_panel ] = array();
			}

			$this->pages[ $administration_panel ][ $slug ] = $page;

			return true;
		}

		/**
		 * Gets a specific admin page.
		 *
		 * @since 1.0.0
		 *
		 * @param string $slug                 Page slug.
		 * @param string $administration_panel Optional. Either 'site', 'network' or 'user'.
		 *                                     Default 'site'.
		 * @return Admin_Page Admin page instance, or null if it does not exist.
		 */
		public function get( $slug, $administration_panel = 'site' ) {
			if ( ! $this->exists( $slug, $administration_panel ) ) {
				return null;
			}

			$slug = $this->get_prefix() . $slug;

			return $this->pages[ $administration_panel ][ $slug ];
		}

		/**
		 * Checks whether a specific admin page exists.
		 *
		 * @since 1.0.0
		 *
		 * @param string $slug                 Page slug.
		 * @param string $administration_panel Optional. Either 'site', 'network' or 'user'.
		 *                                     Default 'site'.
		 * @return bool True if the admin page exists, false otherwise.
		 */
		public function exists( $slug, $administration_panel = 'site' ) {
			$slug = $this->get_prefix() . $slug;

			return isset( $this->pages[ $administration_panel ][ $slug ] );
		}

		/**
		 * Removes an admin page.
		 *
		 * This method must be called before the 'admin_menu' action.
		 *
		 * @since 1.0.0
		 *
		 * @param string $slug                 Page slug.
		 * @param string $administration_panel Optional. Either 'site', 'network' or 'user'.
		 *                                     Default 'site'.
		 * @return bool True on success, false on failure.
		 */
		public function remove( $slug, $administration_panel = 'site' ) {
			if ( ! $this->exists( $slug, $administration_panel ) ) {
				return false;
			}

			$slug = $this->get_prefix() . $slug;

			unset( $this->pages[ $administration_panel ][ $slug ] );

			return true;
		}

		/**
		 * Adds all the available admin pages to the WordPress menu.
		 *
		 * @since 1.0.0
		 */
		protected function add_pages() {
			$administration_panel = 'site';
			if ( is_network_admin() ) {
				$administration_panel = 'network';
			} elseif ( is_user_admin() ) {
				$administration_panel = 'user';
			}

			if ( ! isset( $this->pages[ $administration_panel ] ) ) {
				return;
			}

			foreach ( $this->pages[ $administration_panel ] as $slug => $page ) {
				$callback = 'add_menu_page';
				$args     = array(
					$page->title,
					$page->menu_title,
					$page->capability,
					$slug,
					array( $page, 'render' ),
				);

				if ( $page->parent_slug ) {
					$callback = 'add_submenu_page';
					array_unshift( $args, $page->parent_slug );
				} elseif ( $page->skip_menu ) {
					$callback = 'add_submenu_page';
					array_unshift( $args, '' );
				} else {
					$args[] = $page->icon_url;
					$args[] = $page->position;
				}

				$hook_suffix = call_user_func_array( $callback, $args );

				add_action( 'load-' . $hook_suffix, array( $page, 'handle_request' ), 10, 0 );

				$this->hook_suffix_map[ $administration_panel ][ $hook_suffix ] = $slug;
				$page->hook_suffix = $hook_suffix;
			}
		}

		/**
		 * Registers all available settings pages content.
		 *
		 * @since 1.0.0
		 */
		protected function register_settings() {
			$administration_panel = 'site';
			if ( is_network_admin() ) {
				$administration_panel = 'network';
			} elseif ( is_user_admin() ) {
				$administration_panel = 'user';
			}

			if ( ! isset( $this->pages[ $administration_panel ] ) ) {
				return;
			}

			foreach ( $this->pages[ $administration_panel ] as $slug => $page ) {
				if ( ! is_a( $page, Settings_Page::class ) ) {
					continue;
				}

				$page->register();
			}
		}

		/**
		 * Enqueues assets for the current admin page.
		 *
		 * @since 1.0.0
		 *
		 * @param string $hook_suffix Hook suffix of the current admin page.
		 */
		protected function enqueue_assets( $hook_suffix ) {
			$administration_panel = 'site';
			if ( is_network_admin() ) {
				$administration_panel = 'network';
			} elseif ( is_user_admin() ) {
				$administration_panel = 'user';
			}

			if ( ! isset( $this->hook_suffix_map[ $administration_panel ][ $hook_suffix ] ) ) {
				return;
			}

			$slug = $this->hook_suffix_map[ $administration_panel ][ $hook_suffix ];

			$this->pages[ $administration_panel ][ $slug ]->enqueue_assets();
		}

		/**
		 * Sets up all action and filter hooks for the service.
		 *
		 * This method must be implemented and then be called from the constructor.
		 *
		 * @since 1.0.0
		 */
		protected function setup_hooks() {
			$this->actions = array(
				array(
					'name'     => 'admin_menu',
					'callback' => array( $this, 'add_pages' ),
					'priority' => 10,
					'num_args' => 0,
				),
				array(
					'name'     => 'network_admin_menu',
					'callback' => array( $this, 'add_pages' ),
					'priority' => 10,
					'num_args' => 0,
				),
				array(
					'name'     => 'user_admin_menu',
					'callback' => array( $this, 'add_pages' ),
					'priority' => 10,
					'num_args' => 0,
				),
				array(
					'name'     => 'admin_init',
					'callback' => array( $this, 'register_settings' ),
					'priority' => 10,
					'num_args' => 0,
				),
				array(
					'name'     => 'admin_enqueue_scripts',
					'callback' => array( $this, 'enqueue_assets' ),
					'priority' => 10,
					'num_args' => 1,
				),
			);
		}
	}

endif;
