<?php
/**
 * Handles admin pages.
 *
 * @package northmill-online-shared
 */

declare(strict_types=1);

namespace Northmill_Online_Shared\Modules\Admin\Pages;

use Closure;
use Northmill_Online_Shared\Abstracts\Hub_API\Hub_Accounts\Hub_Account_Setup;
use Northmill_Online_Shared\Helpers\Hook_Helpers;
use Northmill_Online_Shared\Modules\Abstract_Module;
use Northmill_Online_Shared\Modules\Admin\Pages\Models\Admin_Page_Collection;
use Northmill_Online_Shared\Modules\Admin\Pages\Page\Admin_Settings;
use Northmill_Online_Shared\Modules\Admin\Pages\Page\Admin_Status;
use Northmill_Online_Shared\Modules\Admin\Pages\Page\Admin_Sync;
use Northmill_Online_Shared\Modules\Admin\Pages\Page\Admin_Sync_Settings;
use Northmill_Online_Shared\Modules\Admin\Pages\Page\Admin_Tools;
use Northmill_Online_Shared\Modules\Admin\Pages\Page\Custom_Page;

/**
 * Class Admin_Pages
 */
class Admin_Pages_Module extends Abstract_Module {

	/**
	 * Collection of all admin pages.
	 *
	 * @var Admin_Page_Collection[]
	 */
	private array $page_collections = array();

	/**
	 * Pages prepped for setup.
	 *
	 * @var Abstract_Admin_Page[]
	 */
	private array $prepped_pages = array();

	/**
	 * Stores main pages per page collection.
	 *
	 * @var array
	 */
	private array $main_pages = array();

	/**
	 * Page Settings array.
	 *
	 * @var array
	 */
	private array $page_settings = array();

	/**
	 * Flag indicating whether to create a settings page.
	 *
	 * @var bool
	 */
	private bool $create_settings_page = false;

	/**
	 * Flag indicating whether to create a status page.
	 *
	 * @var bool
	 */
	private bool $create_status_page = true;

	/**
	 * Module setup.
	 */
	public function setup(): void {
		add_action( 'admin_menu', array( $this, 'render_menu' ) );

		$this->register_pages();
	}

	/**
	 * Sets whether to create the settings page or not.
	 *
	 * @param bool $create_settings_page Whether to enable or disable.
	 *
	 * @return void
	 */
	public function set_create_settings_page( bool $create_settings_page ): void {
		$this->create_settings_page = $create_settings_page;
	}

	/**
	 * Sets whether to create status page or not.
	 *
	 * @param bool $create_status_page Whether to create status page.
	 *
	 * @return void
	 *
	 * @used Northmill Checkout
	 */
	public function set_create_status_page( bool $create_status_page ): void {
		$this->create_status_page = $create_status_page;
	}

	/**
	 * Register admin pages.
	 *
	 * @return void
	 */
	public function register_pages(): void {
		Hook_Helpers::do_scoped_action(
			'northmill_online_shared_register_admin_pages',
			$this
		);

		if ( Hub_Account_Setup::is_setting_up_hub_accounts() ) {
			$is_main_page = false;

			if ( empty( $this->main_pages[ $this->shared->package_info->get_service_name() ] ) ) {
				$is_main_page = true;
			}
			$this->add_page(
				Admin_Sync::class,
				'sync',
				array( $this->shared->package_info->package_data, 'get_name' ),
				$this->shared->package_info->get_service_name(),
				array( $this->shared->package_info->package_data, 'get_name' ),
				$is_main_page
			);
		}

		if ( $this->create_settings_page ) {
			$is_main_page = false;

			if ( empty( $this->main_pages[ $this->shared->package_info->get_service_name() ] ) ) {
				$is_main_page = true;
			}
			$this->add_page(
				Admin_Settings::class,
				'settings',
				array( $this->shared->package_info->package_data, 'get_name' ),
				$this->shared->package_info->get_service_name(),
				function () {
					return __( 'Settings', 'northmill-online-shared' );
				},
				$is_main_page
			);
		}

		$has_syncer = Admin_Sync_Settings::get_syncer_class();
		if ( $has_syncer ) {
			$this->add_page(
				Admin_Sync_Settings::class,
				'sync-settings',
				function () {
					return $this->shared->package_info->package_data->get_name() . ' ' . __( 'Settings sync', 'northmill-online-shared' );
				},
				$this->shared->package_info->get_service_name(),
				function () {
					return __( 'Settings sync', 'northmill-online-shared' );
				},
				empty( $this->main_pages[ $this->shared->package_info->get_service_name() ] )
			);
		}

		if ( $this->create_status_page ) {
			$this->add_page(
				Admin_Status::class,
				'status',
				function () {
					return $this->shared->package_info->package_data->get_name() . ' ' . __( 'Status', 'northmill-online-shared' );
				},
				$this->shared->package_info->get_service_name(),
				function () {
					return __( 'Status', 'northmill-online-shared' );
				},
				empty( $this->main_pages[ $this->shared->package_info->get_service_name() ] )
			);
		}

		$this->add_page(
			Admin_Tools::class,
			'tools',
			function () {
				return $this->shared->package_info->package_data->get_name() . ' ' . __( 'Tools', 'northmill-online-shared' );
			},
			$this->shared->package_info->get_service_name(),
			function () {
				return __( 'Tools', 'northmill-online-shared' );
			},
			empty( $this->main_pages[ $this->shared->package_info->get_service_name() ] )
		);

		$this->create_page_collections();
	}

	/**
	 * Get a page by id.
	 *
	 * @param string $collection_key Collection key.
	 * @param string $page_id        Page id. Only needed if $is_main_page is false.
	 *
	 * @return null|Abstract_Admin_Page
	 */
	public function get_page( string $collection_key, string $page_id = '' ): ?Abstract_Admin_Page {
		$collection = $this->page_collections[ $collection_key ] ?? null;
		return $collection->pages[ $page_id ] ?? null;
	}

	/**
	 * Get the main page of an Admin_Page_Collection.
	 *
	 * @param Admin_Page_Collection|string $collection Collection or collection key.
	 *
	 * @return ?Abstract_Admin_Page Admin page, or null if Collection or page doesn't exist.
	 */
	public function get_collection_main_page( Admin_Page_Collection|string $collection ): ?Abstract_Admin_Page {
		if ( is_string( $collection ) ) {
			if ( isset( $this->page_collections[ $collection ] ) ) {
				$collection = $this->page_collections[ $collection ];
			} else {
				return null;
			}
		}

		foreach ( $collection->pages as $page ) {
			if ( $page->is_main_page ) {
				return $page;
			}
		}

		return null;
	}

	/**
	 * Register the admin menu.
	 */
	public function render_menu(): void {

		foreach ( $this->page_collections as $page_collection ) {
			$main_page = $this->get_collection_main_page( $page_collection );

			if ( ! $main_page->show_in_menu() ) {
				continue;
			}

			$main_page_title = $main_page->get_page_title();

			$main_page_menu_title = $main_page->get_menu_title();

			add_menu_page(
				$main_page_title,
				$main_page_title,
				'manage_options',
				$main_page->menu_slug,
				array( $main_page, 'render_settings_page' ),
				$this->shared->assets->get_shared_img_url( 'nm-logo.png' ),
				90
			);

			if ( count( $page_collection->pages ) > 1 ) {
				add_submenu_page(
					$main_page->menu_slug,
					$main_page_title,
					$main_page_menu_title,
					'manage_options',
					$main_page->menu_slug,
					array( $main_page, 'render_settings_page' ),
					90
				);

				foreach ( $page_collection->pages as $page ) {
					if ( ! $page->is_main_page && $page->show_in_menu() ) {

						$page_title = $page->get_page_title();

						$page_menu_title = $page->get_menu_title();
						add_submenu_page(
							$main_page->menu_slug,
							$page_title,
							$page_menu_title,
							'manage_options',
							$page->menu_slug,
							array( $page, 'render_settings_page' ),
							90
						);
					}
				}
			}
		}
	}

	/**
	 * Try getting settings for admin page.
	 *
	 * @param string $collection_key Collection key.
	 * @param string $page_id Page id.
	 *
	 * @return array|null Array of settings or null if no settings found.
	 */
	public function get_page_settings( string $collection_key, string $page_id = '' ): ?array {
		$page = $this->get_page( $collection_key, $page_id );
		if ( is_null( $page ) ) {
			return null;
		}

		// TODO: Maybe change back to not have static settings ID?
		$page_settings_id = Abstract_Admin_Page::get_settings_id();

		if ( $page_settings_id ) {
			if ( ! isset( $this->page_settings[ $page_settings_id ] ) ) {
				$this->page_settings[ $page_settings_id ] = $this->shared->admin->settings_fields->get_option(
					'setting',
					$page_settings_id
				);
			}
			return $this->page_settings[ $page_settings_id ];

		} else {
			return null;
		}
	}

	/**
	 * Set settings for admin page.
	 *
	 * @param string $collection_key Collection key.
	 * @param string $page_id Page id.
	 * @param array  $settings Settings to set.
	 *
	 * @return bool Whether settings were set.
	 */
	public function set_page_settings( string $collection_key, string $page_id, array $settings ): bool {
		$page = $this->get_page( $collection_key, $page_id );
		if ( is_null( $page ) ) {
			return false;
		}

		$page_settings_id = Abstract_Admin_Page::get_settings_id();

		if ( $page_settings_id ) {
			$this->page_settings[ $page_settings_id ] = $settings;
			return $this->shared->admin->settings_fields->update_option( 'setting', $page_settings_id, $settings );
		}
		return false;
	}

	/**
	 * Prepare admin page.
	 *
	 * @param string               $class_name                   Admin page class.
	 * @param string               $page_id                      Page id.
	 * @param string|array|Closure $page_title                   Page title. Can be a callback to get title, for translation to work.
	 * @param string|null          $page_collection_key          Collection key of page.
	 * @param string|array|Closure $page_menu_title              Page menu title. Can be a callback to get title, for translation to work.
	 * @param bool                 $is_main_page                 Whether page is the collection main page.
	 * @param array                $extra                        Extra data for creating page.
	 * @param array|Closure|null   $script_localization_callback Optional callback to get script localization data.
	 * @param array|Closure|null   $script_dependencies_callback Optional callback to get script dependency data.
	 *
	 * @return void
	 */
	public function add_page(
		string $class_name,
		string $page_id,
		array|Closure $page_title,
		?string $page_collection_key = null,
		null|array|Closure $page_menu_title = null,
		bool $is_main_page = false,
		array $extra = array(),
		array|Closure|null $script_localization_callback = null,
		array|Closure|null $script_dependencies_callback = null
	): void {
		if ( class_exists( $class_name ) ) {
			if ( is_null( $page_collection_key ) ) {
				$page_collection_key = $this->shared->package_info->get_service_name();
			}
			if ( $is_main_page ) {
				$this->main_pages[ $page_collection_key ] = true;
			}

			if ( is_a( $class_name, Custom_Page::class, true ) ) {
				if ( ! empty( $extra['custom_callback'] ) && is_callable( $extra['custom_callback'] ) ) {
					$this->prepped_pages[] = new Custom_Page(
						$extra['custom_callback'],
						$page_collection_key,
						$page_id,
						$page_title,
						$page_menu_title,
						$is_main_page,
						$script_localization_callback,
						$script_dependencies_callback
					);
				}
			} else {
				$this->prepped_pages[] = new $class_name(
					$page_collection_key,
					$page_id,
					$page_title,
					$page_menu_title,
					$is_main_page,
					$script_localization_callback,
					$script_dependencies_callback
				);
			}
		}
	}

	/**
	 * Create structure for page collections.
	 *
	 * @return void
	 */
	private function create_page_collections(): void {
		if ( empty( $this->prepped_pages ) ) {
			return;
		}

		$collections = array();
		foreach ( $this->prepped_pages as $page ) {
			$collections[ $page->page_collection_key ]['pages'][ $page->page_id ] = $page;
		}

		foreach ( $collections as $collection ) {
			foreach ( $collection['pages'] as $page ) {
				if ( $page->is_main_page ) {
					$this->page_collections[ $page->page_collection_key ] = new Admin_Page_Collection( $collection );
					break;
				}
			}
		}
	}
}
