<?php

namespace SugarCalendarBookings\Admin;

use SugarCalendarBookings\Admin\Settings\AntispamSettingsTab;
use SugarCalendarBookings\Admin\Settings\AvailabilitySettingsTab;
use SugarCalendarBookings\Admin\Settings\EmailEditPage;
use SugarCalendarBookings\Admin\Settings\EmailsSettingsTab;
use SugarCalendarBookings\Admin\Settings\GeneralSettingsTab;
use SugarCalendarBookings\Admin\Settings\ImportExportSettingsTab;
use SugarCalendarBookings\Admin\Settings\MiscSettingsTab;
use SugarCalendarBookings\Admin\Settings\IntegrationsSettingsTab;
use SugarCalendarBookings\Admin\Settings\LicenseSettingsTab;
use SugarCalendarBookings\Admin\Settings\PaymentsSettingsTab;
use SugarCalendarBookings\Appointments\Admin\AppointmentDetailsPage;
use SugarCalendarBookings\Appointments\Admin\AppointmentsListPage;
use SugarCalendarBookings\Calendar\Admin\CalendarPage;
use SugarCalendarBookings\Customers\Admin\CustomersListPage;
use SugarCalendarBookings\Employees\Admin\EmployeesEducationPage;
use SugarCalendarBookings\Locations\Admin\LocationsListPage;
use SugarCalendarBookings\Payments\Admin\PaymentsDetailsPage;
use SugarCalendarBookings\Payments\Admin\PaymentsListPage;
use SugarCalendarBookings\Plugin;
use SugarCalendarBookings\Services\Admin\ServicesListPage;
use SugarCalendarBookings\Support\Admin\AbstractPage;
use SugarCalendarBookings\Support\Admin\Notices;
use SugarCalendarBookings\Support\Admin\PageLayouts\GeneralLayout;
use SugarCalendarBookings\Support\Admin\PageLayouts\LayoutInterface;
use SugarCalendarBookings\Support\Admin\PageLayouts\TabbedLayout;
use SugarCalendarBookings\Support\Bootable;
use SugarCalendarBookings\Support\Helpers\Helpers;
use SugarCalendarBookings\Support\Helpers\UI;
use SugarCalendarBookings\Support\Helpers\WP;
use SugarCalendarBookings\Support\RequestType;
use SugarCalendarBookings\Support\DataProcessing\InputProcessor;
use SugarCalendarBookings\Support\Admin\AbstractEducationPage;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class Area registers and process all wp-admin display functionality.
 *
 * @since 1.0.0
 */
class Area implements Bootable {

	/**
	 * Slug of the whole admin area.
	 *
	 * @since 1.0.0
	 *
	 * @var string Admin area slug.
	 */
	const SLUG = 'sugar-calendar-bookings';

	/**
	 * Current page instance.
	 *
	 * @since 1.0.0
	 *
	 * @var AbstractPage Current page.
	 */
	private $current_page;

	/**
	 * Register hooks.
	 *
	 * @since 1.0.0
	 * @since 1.0.0 Added the Admin\Events hooks.
	 * @since 1.1.0 Moved the current page setup to admin_init.
	 *
	 * @return void
	 */
	public function hooks() {

		// Setup the current page vars at negative priority so page hooks can register on admin_init.
		$this->setup_current_page();

		// Admin body class.
		add_filter( 'admin_body_class', [ $this, 'body_class' ] );

		// Enqueue assets.
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );

		// Admin header.
		add_action( 'in_admin_header', [ $this, 'display_admin_header' ] );

		// Handle POST requests.
		add_action( 'admin_init', [ $this, 'handle_post' ] );

		// Remove unrelated notices.
		add_action( 'admin_print_scripts', [ $this, 'hide_unrelated_notices' ] );
	}

	/**
	 * Setup the current page.
	 *
	 * @since 1.1.0
	 */
	public function setup_current_page() {

		$this->current_page = $this->get_current_page();

		// Bail if the page doesn't exist.
		if ( $this->current_page !== null ) {
			$layout = $this->get_current_page_layout();

			if ( ! empty( $layout ) ) {
				$this->current_page->set_layout( $layout );
			}

			$this->current_page->hooks();
		}
	}

	/**
	 * Get page class overrides.
	 *
	 * Can override this to replace pages with other versions.
	 *
	 * @since 1.2.0
	 *
	 * @return array<class-string, class-string> Map of page class => replacement page class.
	 */
	protected function get_page_overrides(): array {

		return [];
	}

	/**
	 * Get the list of page classes.
	 *
	 * @since 1.0.0
	 * @since 1.2.0 Added the EmployeesEducationPage class.
	 * @since 1.2.0 Added support for page overrides.
	 *
	 * @return array<class-string<AbstractPage>>
	 */
	protected function get_pages() {

		$pages = [
			CalendarPage::class,
			AppointmentsListPage::class,
			AppointmentDetailsPage::class,
			ServicesListPage::class,
			LocationsListPage::class,
			CustomersListPage::class,
			EmployeesEducationPage::class,
			PaymentsListPage::class,
			PaymentsDetailsPage::class,
			LicenseSettingsTab::class,
			GeneralSettingsTab::class,
			AvailabilitySettingsTab::class,
			PaymentsSettingsTab::class,
			EmailsSettingsTab::class,
			IntegrationsSettingsTab::class,
			ImportExportSettingsTab::class,
			AntispamSettingsTab::class,
			MiscSettingsTab::class,
			EmailEditPage::class,
		];

		$overrides = $this->get_page_overrides();

		// Filter out pages that should not be loaded or the user cannot access.
		$pages = array_filter(
			$pages,
			fn( $page ) => $page::should_load() && current_user_can( $page::get_capability() )
		);

		return array_map( fn( $page ) => $overrides[ $page ] ?? $page, $pages );
	}

	/**
	 * Get the list of settings tabs.
	 *
	 * @since 1.0.0
	 * @since 1.4.0 Changed visibility from private to protected. Added capability filter.
	 *
	 * @return array<class-string<AbstractPage>>
	 */
	protected function get_settings_tabs() {

		$tabs = [
			LicenseSettingsTab::class,
			GeneralSettingsTab::class,
			AvailabilitySettingsTab::class,
			PaymentsSettingsTab::class,
			EmailsSettingsTab::class,
			IntegrationsSettingsTab::class,
			ImportExportSettingsTab::class,
			AntispamSettingsTab::class,
			MiscSettingsTab::class,
		];

		// Filter out tabs that should not be loaded or the user cannot access.
		$tabs = array_filter(
			$tabs,
			fn( $tab ) => $tab::should_load() && current_user_can( $tab::get_capability() )
		);

		/**
		 * Filter Settings page tabs.
		 *
		 * @since 1.0.0
		 *
		 * @param array $tabs Array of tabs.
		 */
		return (array) apply_filters( 'sugar_calendar_bookings_admin_area_get_settings_tabs', $tabs );
	}

	/**
	 * Get the current page identifier.
	 *
	 * @since 1.0.0
	 * @since 1.2.0 Move out page slug id mapping to get_page_slug_page_id_map().
	 *
	 * @return AbstractPage|null
	 */
	public function get_current_page() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		$page_id      = null;
		$request_type = sugar_calendar_bookings()->get( RequestType::class );

		if ( ! $request_type->is_admin() ) {
			return null;
		}

		$page_slug_page_id_map = $this->get_page_slug_page_id_map();

		// phpcs:disable WordPress.Security.NonceVerification.Recommended
		if ( isset( $_GET['page'] ) && strpos( sanitize_key( $_GET['page'] ), self::SLUG . '-' ) === 0 ) {
			$page_id = str_replace( self::SLUG . '-', '', sanitize_key( $_GET['page'] ) );

			if ( isset( $_GET['view'] ) ) {
				$page_id .= '-' . sanitize_key( $_GET['view'] );
			}
		}
		// phpcs:enable WordPress.Security.NonceVerification.Recommended

		$page_id = $page_slug_page_id_map[ $page_id ] ?? $page_id;

		$page = null;

		foreach ( $this->get_pages() as $page_class ) {
			if ( $page_class::get_id() === $page_id ) {
				$page = new $page_class();

				break;
			}
		}

		/**
		 * Allow 3rd party code to modify the current page.
		 *
		 * @since 1.0.0
		 *
		 * @param AbstractPage|null $page Page instance.
		 */
		return apply_filters( 'sugar_calendar_bookings_admin_area_get_current_page', $page );
	}

	/**
	 * Get the page slug to page ID map.
	 *
	 * @since 1.2.0
	 *
	 * @return array
	 */
	protected function get_page_slug_page_id_map() {

		return [
			'services'             => 'services-list',
			'locations'            => 'locations-list',
			'appointments'         => 'appointments-list',
			'appointments-details' => 'appointments-details',
			'customers'            => 'customers-list',
			'employees'            => 'employees-list-lite',
			'payments'             => 'payments-list',
			'settings'             => 'settings-license',
		];
	}

	/**
	 * Get the current page layout.
	 *
	 * @since 1.0.0
	 *
	 * @return LayoutInterface|null
	 */
	private function get_current_page_layout() {

		$tabbed_pages = [
			self::SLUG . '-settings',
		];

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( isset( $_GET['page'] ) && in_array( sanitize_key( $_GET['page'] ), $tabbed_pages, true ) ) {
			$layout = new TabbedLayout( $this->get_settings_tabs() );
		} else {
			$layout = new GeneralLayout();
		}

		/**
		 * Allow 3rd party code to modify the current page layout.
		 *
		 * @since 1.0.0
		 *
		 * @param LayoutInterface $layout Page layout.
		 * @param AbstractPage    $page   Page instance.
		 */
		return apply_filters( 'sugar_calendar_bookings_admin_area_get_current_page_layout', $layout, $this->current_page );
	}

	/**
	 * Check if the current page matches the provided id.
	 *
	 * @since 1.0.0
	 *
	 * @param string $id Page id.
	 *
	 * @return bool
	 */
	public function is_page( $id = null ) {

		if ( $id === null ) {
			return $this->current_page !== null;
		}

		return $id === $this->current_page->get_id();
	}

	/**
	 * Display the current page.
	 *
	 * @since 1.0.0
	 */
	public function display() {

		// Bail if the page doesn't exist.
		if ( $this->current_page === null ) {
			return;
		}

		$this->current_page->display();
	}

	/**
	 * Handle POST requests.
	 *
	 * @since 1.0.0
	 */
	public function handle_post() { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		// Bail if it's an AJAX request.
		if ( sugar_calendar_bookings()->get( RequestType::class )->is_ajax() ) {
			return;
		}

		// Bail if the page doesn't exist.
		if ( $this->current_page === null ) {
			return;
		}

		// Bail if post data is empty.
		if ( empty( $_POST ) ) {
			return;
		}

		// Bail if we're not submitting a form.
		if ( ! isset( $_POST[ self::SLUG . '-submit' ] ) ) {
			return;
		}

		// Bail if request checks fail.
		if ( ! check_admin_referer( self::SLUG ) ) {
			return;
		}

		$sanitized_data = InputProcessor::process(
		  // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			wp_unslash( $_POST[ self::SLUG ] ?? [] ),
			$this->current_page->get_post_data_processing_rules()
		);

		if ( is_wp_error( $sanitized_data ) ) {
			Plugin::get( Notices::class )->error( $sanitized_data->get_error_message() );

			return;
		}

		// Let the current page handle the request.
		$this->current_page->handle_post( $sanitized_data );
	}

	/**
	 * Add the plugin body class.
	 *
	 * @since 1.0.0
	 *
	 * @param string $classes Body classes.
	 *
	 * @return string
	 */
	public function body_class( $classes = '' ) {

		// Bail if not in an admin page.
		if ( $this->current_page === null ) {
			return $classes;
		}

		$classes .= ' sugar-calendar-bookings';

		if ( sugar_calendar_bookings()->is_pro() ) {
			$classes .= " {$classes}-pro";
		} else {
			$classes .= " {$classes}-lite";
		}

		return $classes;
	}

	/**
	 * Display the admin header.
	 *
	 * @since 1.0.0
	 */
	public function display_admin_header() {

		// Bail if not in an admin page.
		if ( $this->current_page === null ) {
			return;
		}

		UI::header();
	}

	/**
	 * Enqueue assets.
	 *
	 * @since 1.0.0
	 * @since 1.0.0 Add wp-date script as a dependency for admin-event-meta-box script.
	 *
	 * @param string $hook The current admin page.
	 */
	public function enqueue_assets( $hook ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		// Bail if not in an admin page.
		if ( $this->current_page === null ) {
			return;
		}

		// Register dependencies.
		wp_register_style(
			'sugar-calendar-bookings-vendor-jquery-confirm',
			SCBOOKINGS_PLUGIN_URL . 'assets/lib/jquery-confirm/jquery-confirm.min.css',
			[],
			'3.3.4'
		);

		wp_register_script(
			'sugar-calendar-bookings-vendor-jquery-confirm',
			SCBOOKINGS_PLUGIN_URL . 'assets/lib/jquery-confirm/jquery-confirm.min.js',
			[ 'jquery' ],
			'3.3.4',
			true
		);

		/**
		 * Fires before enqueue plugin assets.
		 *
		 * @since 1.0.0
		 *
		 * @param AbstractPage $page Current page.
		 */
		do_action( 'sugar_calendar_bookings_admin_area_enqueue_assets_before', $this->current_page );

		$assets = Plugin::get( Assets::class );

		if ( $assets->is_dev() ) {
			$assets->enqueue_dev_runtime();
		}

		if ( ! wp_script_is( 'react-jsx-runtime', 'registered' ) ) {
			wp_register_script(
				'react-jsx-runtime',
				SCBOOKINGS_PLUGIN_URL . 'assets/lib/react-jsx-runtime.js',
				[ 'react' ],
				'18.3.1',
				true
			);
		}

		$manifest = $assets->get_asset_manifest( 'admin/common' );

		if ( $manifest !== false ) {
			wp_enqueue_script(
				'sugar-calendar-bookings-admin-common',
				$assets->get_src( 'admin/common.js' ),
				$manifest['dependencies'],
				$assets->is_dev() ? $manifest['version'] : SCBOOKINGS_PLUGIN_VERSION,
				true
			);

			wp_set_script_translations(
				'sugar-calendar-bookings-admin-common',
				'sugar-calendar-bookings-scheduling-appointments-lite',
				SCBOOKINGS_PLUGIN_PATH . 'assets/languages'
			);

			wp_enqueue_style(
				'sugar-calendar-bookings-admin-common',
				$assets->get_src( 'admin/common.css' ),
				[],
				$assets->is_dev() ? $manifest['version'] : SCBOOKINGS_PLUGIN_VERSION
			);
		}

		// Current page assets.
		$this->enqueue_current_page_assets();

		/**
		 * Fires after enqueue plugin assets.
		 *
		 * @since 1.0.0
		 *
		 * @param AbstractPage $page Current page.
		 */
		do_action( 'sugar_calendar_bookings_admin_area_enqueue_assets', $this->current_page );
	}

	/**
	 * Enqueue current page assets.
	 *
	 * @since 1.0.0
	 */
	private function enqueue_current_page_assets() {

		$assets     = Plugin::get( Assets::class );
		$asset_path = 'admin/pages/' . $this->current_page->get_id();
		$manifest   = $assets->get_asset_manifest( $asset_path );

		if ( $manifest === false ) {
			return;
		}

		$handle = self::SLUG . '-' . str_replace( [ '/', '_' ], '-', $asset_path );

		wp_enqueue_script(
			$handle,
			$assets->get_src( $asset_path . '.js' ),
			$manifest['dependencies'],
			$assets->is_dev() ? $manifest['version'] : SCBOOKINGS_PLUGIN_VERSION,
			true
		);

		wp_set_script_translations(
			$handle,
			'sugar-calendar-bookings-scheduling-appointments-lite',
			SCBOOKINGS_PLUGIN_PATH . 'assets/languages'
		);

		wp_enqueue_style(
			$handle,
			$assets->get_src( $asset_path . '.css' ),
			[],
			$assets->is_dev() ? $manifest['version'] : SCBOOKINGS_PLUGIN_VERSION
		);

		wp_localize_script(
			$handle,
			'sugar_calendar_bookings_admin',
			array_merge(
				$this->get_script_data(),
				$this->current_page->get_script_data()
			)
		);
	}

	/**
	 * Get common script data.
	 *
	 * @since 1.0.0
	 *
	 * @return array
	 */
	private function get_script_data() {

		return [
			'ajax_url'    => WP::ajax_url(),
			'admin_url'   => WP::admin_url(),
			'build_url'   => SCBOOKINGS_PLUGIN_URL . 'assets/dist/',
			'is_pro'      => sugar_calendar_bookings()->is_pro(),
			'upgrade_url' => Helpers::get_utm_url(
				'https://sugarcalendar.com/pricing/bookings/',
				[
					'medium'  => 'upgrade-modal',
					'content' => 'Upgrade to Pro',
				]
			),
		];
	}

	/**
	 * Remove 3rd party admin notices.
	 *
	 * @since 1.0.0
	 */
	public function hide_unrelated_notices() {

		// Bail if not in an admin page.
		if ( $this->current_page === null ) {
			return;
		}

		$this->remove_unrelated_actions( 'user_admin_notices' );
		$this->remove_unrelated_actions( 'admin_notices' );
		$this->remove_unrelated_actions( 'all_admin_notices' );
		$this->remove_unrelated_actions( 'network_admin_notices' );
	}

	/**
	 * Remove 3rd party notices based on the provided action hook.
	 *
	 * @since 1.0.0
	 *
	 * @param string $action The name of the action.
	 */
	private function remove_unrelated_actions( $action ) {

		global $wp_filter;

		if ( empty( $wp_filter[ $action ]->callbacks ) || ! is_array( $wp_filter[ $action ]->callbacks ) ) {
			return;
		}

		foreach ( $wp_filter[ $action ]->callbacks as $priority => $hooks ) {
			foreach ( $hooks as $name => $arr ) {
				if (
					( // Cover object method callback case.
						is_array( $arr['function'] ) &&
						isset( $arr['function'][0] ) &&
						is_object( $arr['function'][0] ) &&
						strpos( strtolower( get_class( $arr['function'][0] ) ), 'sugarcalendarbookings' ) !== false
					) ||
					( // Cover class static method callback case.
						! empty( $name ) &&
						strpos( strtolower( $name ), 'sugarcalendarbookings' ) !== false
					)
				) {
					continue;
				}

				unset( $wp_filter[ $action ]->callbacks[ $priority ][ $name ] );
			}
		}
	}

	/**
	 * Whether we are on a Sugar Calendar Bookings admin page.
	 *
	 * @since 1.0.0
	 *
	 * @return bool
	 */
	public function is_admin_page() {

		return ! empty( $this->current_page ) && $this->current_page instanceof AbstractPage;
	}

	/**
	 * Whether we are on a Calendar page.
	 *
	 * @since 1.2.1
	 *
	 * @return bool
	 */
	public function is_calendar_page() {

		return $this->current_page instanceof CalendarPage;
	}

	/**
	 * Whether we are on an education page.
	 *
	 * @since 1.2.0
	 *
	 * @return bool
	 */
	public function is_education_page() {

		return $this->current_page instanceof AbstractEducationPage;
	}
}
