<?php
namespace RemoveAdminNotices;

use WP_PowerSuite\Core\AbstractModule;
use WP_PowerSuite\Core\AdminAssetContext;

class Module extends AbstractModule {

	private const MODULE_ID = 'remove-admin-notices';
	private const MODULE_VERSION = '1.2.0';
	private const STYLE_HANDLE = 'wp-powersuite-remove-admin-notices';
	private const SCRIPT_HANDLE = 'wp-powersuite-remove-admin-notices';

	private ?array $settings_cache = null;

	public function getId(): string {
		return self::MODULE_ID;
	}

	public function getName(): string {
		return __( 'Hide Admin Notices', 'wp-powersuite' );
	}

	public function getVersion(): string {
		return self::MODULE_VERSION;
	}

	public function getDescription(): string {
		return __( 'Hides admin notices from every screen by collecting them into a toolbar panel you open when needed.', 'wp-powersuite' );
	}

	public function getContext(): string|array {
		return 'admin';
	}

	/**
	 * @return array<string, bool>
	 */
	public static function getDefaultSettings(): array {
		return [
			'hide_core'    => true,
			'hide_plugins' => true,
			'hide_themes'  => false,
			'hide_errors'  => false,
		];
	}

	/**
	 * @param array<string, mixed> $settings
	 * @return array<string, bool>
	 */
	public static function normalizeSettings( array $settings ): array {
		$defaults = self::getDefaultSettings();
		$normalized = [];

		foreach ( $defaults as $key => $default ) {
			$normalized[ $key ] = ! empty( $settings[ $key ] );
		}

		return $normalized;
	}

	/**
	 * @param array<string, bool> $settings
	 */
	public static function hasAnyHideSetting( array $settings ): bool {
		return ! empty( $settings['hide_core'] )
			|| ! empty( $settings['hide_plugins'] )
			|| ! empty( $settings['hide_themes'] )
			|| ! empty( $settings['hide_errors'] );
	}

	/**
	 * @param array<string, bool> $settings
	 */
	public static function hidesAllStandardSources( array $settings ): bool {
		return ! empty( $settings['hide_core'] )
			&& ! empty( $settings['hide_plugins'] )
			&& ! empty( $settings['hide_themes'] );
	}

	/**
	 * Scoped pre-hide CSS to avoid notice flash before JS collects them.
	 *
	 * @param array<string, bool> $settings
	 */
	public static function buildPrehideCss( array $settings ): string {
		if ( ! self::hasAnyHideSetting( $settings ) ) {
			return '';
		}

		if ( is_customize_preview() ) {
			return '';
		}

		if ( self::hidesAllStandardSources( $settings ) || ! empty( $settings['hide_errors'] ) ) {
			return self::buildScopedPrehideCss( ! empty( $settings['hide_errors'] ) );
		}

		return self::buildPartialPrehideCss( $settings );
	}

	/**
	 * @param array<string, bool> $settings
	 */
	private static function buildPartialPrehideCss( array $settings ): string {
		$rules = [];

		if ( ! empty( $settings['hide_core'] ) ) {
			$rules[] = '#wpbody-content .update-nag:not([data-wpps-plugin="true"])';
			$rules[] = '#wpbody-content .update-message:not([data-wpps-plugin="true"])';
			$rules[] = '#wpbody-content > .update-nag:not([data-wpps-plugin="true"])';
		}

		if ( ! empty( $settings['hide_plugins'] ) ) {
			$rules[] = '#wpbody-content .notice.is-dismissible:not(.notice-error,.notice-warning,[data-wpps-plugin="true"])';
		}

		if ( empty( $rules ) ) {
			return '';
		}

		return implode( ",\n", $rules ) . " {\n\tposition: absolute !important;\n\tvisibility: hidden !important;\n}\n";
	}

	private static function buildScopedPrehideCss( bool $include_errors ): string {
		$exclude = ':not(#plugin-activated-successfully,.system-notice,.hidden,.inline,.wcml-notice,[data-wpps-plugin="true"])';
		$selectors = self::scopedPrehideSelectors( $exclude, $include_errors );

		if ( $selectors === [] ) {
			return '';
		}

		return implode( ",\n", $selectors ) . " {\n\tposition: absolute !important;\n\tvisibility: hidden !important;\n}\n";
	}

	/**
	 * @return list<string>
	 */
	private static function scopedPrehideSelectors( string $exclude, bool $include_errors ): array {
		$wrap = '#wpbody-content > .wrap > ';
		$wrap_div = '#wpbody-content > .wrap > div:not(#loco-notices,#loco-content) > ';
		$wrap_form = '#wpbody-content > form > .wrap > ';
		$wc_form = '#wpbody-content > .wrap.woocommerce > form > ';

		$notice = ".notice{$exclude}";
		$types = [
			"{$wrap}{$notice}",
			"{$wrap}.notice-error",
			"{$wrap}.error:not(.hidden)",
			"{$wrap}.notice-info",
			"{$wrap}.notice-information",
			"{$wrap}#message:not(.updated)",
			"{$wrap}.notice-warning:not(.hidden)",
			"{$wrap}.notice-success:not(.updated,#plugin-activated-successfully)",
			"{$wrap}.notice-updated",
			"{$wrap}.updated:not(.inline)",
			"{$wrap}.update-nag",
			"{$wrap_div}{$notice}",
			"{$wrap_div}.notice-error",
			"{$wrap_div}.error:not(.hidden)",
			"{$wrap_div}.notice-info",
			"{$wrap_div}.notice-warning:not(.hidden)",
			"{$wrap_div}.notice-success",
			"{$wrap_div}.updated:not(.inline)",
			"{$wrap_div}.update-nag",
			"{$wrap_form}{$notice}",
			"{$wrap_form}.notice-error",
			"{$wrap_form}.error:not(.hidden)",
			"{$wrap_form}#message",
			"{$wrap_form}.notice-warning:not(.hidden)",
			"{$wrap_form}.notice-success",
			"{$wrap_form}.updated:not(.inline)",
			"{$wrap_form}.update-nag",
			"{$wc_form}{$notice}",
			"{$wc_form}.notice-error",
			"{$wc_form}.error:not(.hidden)",
			"{$wc_form}#message",
			"{$wc_form}.notice-warning:not(.hidden)",
			"{$wc_form}.notice-success",
			"{$wc_form}.updated:not(.inline)",
			"{$wc_form}.update-nag",
			'#wpbody-content > .notice:not(.otgs-notice,.wcml-notice)',
			'#wpbody-content > .update-nag',
			'#wpbody-content > .jp-connection-banner',
			'#wpbody-content > .jitm-banner',
			'#wpbody-content > .jetpack-jitm-message',
		];

		if ( ! $include_errors ) {
			$types = array_values(
				array_filter(
					$types,
					static function ( string $selector ): bool {
						return ! preg_match( '/\.notice-error|\.error(?::|,|$)/', $selector );
					}
				)
			);
		}

		return $types;
	}

	private function isLicenseActive(): bool {
		return function_exists( 'wp_powersuite_is_license_valid' ) && wp_powersuite_is_license_valid();
	}

	protected function registerHooks(): void {
		if ( ! $this->isLicenseActive() ) {
			return;
		}

		if ( ! is_admin() || is_customize_preview() ) {
			return;
		}

		if ( ! current_user_can( 'manage_options' ) ) {
			add_action( 'admin_enqueue_scripts', [ $this, 'enqueueNonAdminAssets' ] );
			return;
		}

		add_action( 'admin_bar_menu', [ $this, 'addNoticesMenu' ], 100 );
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueAdminAssets' ] );
	}

	public function addNoticesMenu( \WP_Admin_Bar $wp_admin_bar ): void {
		$wp_admin_bar->add_node(
			[
				'id'     => 'wpps-admin-notices',
				'title'  => sprintf(
					'<span class="ab-label">%s</span> <span class="wpps-notice-count" aria-hidden="true"></span>',
					esc_html__( 'Notices', 'wp-powersuite' )
				),
				'href'   => '#wpps-notices-panel',
				'parent' => 'top-secondary',
				'meta'   => [
					'class' => 'wpps-notices-menu-item wpps-notices-menu-item--empty',
					'title' => esc_attr__( 'Click to view hidden admin notices', 'wp-powersuite' ),
				],
			]
		);
	}

	public function enqueueNonAdminAssets( string $hook = '' ): void {
		if ( ! AdminAssetContext::shouldLoadAssets( $hook ) ) {
			return;
		}

		wp_register_style( self::STYLE_HANDLE, false, [], self::MODULE_VERSION );
		wp_enqueue_style( self::STYLE_HANDLE );
		wp_add_inline_style( self::STYLE_HANDLE, $this->getNonAdminHideCss() );
	}

	public function enqueueAdminAssets( string $hook = '' ): void {
		if ( ! AdminAssetContext::shouldLoadAssets( $hook ) ) {
			return;
		}

		$base = defined( 'WP_POWERSUITE_PLUGIN_URL' ) ? WP_POWERSUITE_PLUGIN_URL : '';

		wp_enqueue_style(
			self::STYLE_HANDLE,
			$base . 'modules/' . self::MODULE_ID . '/assets/remove-admin-notices.css',
			[],
			self::MODULE_VERSION
		);

		$settings = $this->getSettings();
		$prehide_css = self::buildPrehideCss( $settings );
		if ( $prehide_css !== '' ) {
			wp_add_inline_style( self::STYLE_HANDLE, $prehide_css );
		}

		wp_enqueue_script(
			self::SCRIPT_HANDLE,
			$base . 'modules/' . self::MODULE_ID . '/assets/remove-admin-notices.js',
			[],
			self::MODULE_VERSION,
			true
		);

		wp_localize_script( self::SCRIPT_HANDLE, 'wppsRemoveAdminNotices', [
			'userId'       => get_current_user_id(),
			'hide_core'    => ! empty( $settings['hide_core'] ),
			'hide_plugins' => ! empty( $settings['hide_plugins'] ),
			'hide_themes'  => ! empty( $settings['hide_themes'] ),
			'hide_errors'  => ! empty( $settings['hide_errors'] ),
			'strings'      => [
				'notices'        => __( 'Notices', 'wp-powersuite' ),
				'panel_label'    => __( 'Admin notices panel', 'wp-powersuite' ),
				'close'          => __( 'Close', 'wp-powersuite' ),
				'empty'          => __( 'No notices to display.', 'wp-powersuite' ),
				'empty_settings' => __( 'No notices to display. Select notice sources to hide in module settings.', 'wp-powersuite' ),
			],
		] );
	}

	private function getNonAdminHideCss(): string {
		return '.notice:not([data-wpps-plugin="true"]),
			.notice-error:not([data-wpps-plugin="true"]),
			.notice-warning:not([data-wpps-plugin="true"]),
			.notice-info:not([data-wpps-plugin="true"]),
			.notice-success:not([data-wpps-plugin="true"]),
			.error:not([data-wpps-plugin="true"]),
			.updated:not([data-wpps-plugin="true"]),
			.update-nag:not([data-wpps-plugin="true"]) {
				display: none !important;
			}
			.wp-powersuite-settings .notice,
			.wp-powersuite-settings .notice-error,
			.wp-powersuite-settings .notice-warning,
			.wp-powersuite-settings .notice-info,
			.wp-powersuite-settings .notice-success,
			.wp-powersuite-module-settings .notice,
			.wp-powersuite-module-settings .notice-error,
			.wp-powersuite-module-settings .notice-warning,
			.wp-powersuite-module-settings .notice-info,
			.wp-powersuite-module-settings .notice-success,
			.wp-powersuite-admin .notice,
			.wp-powersuite-admin .notice-error,
			.wp-powersuite-admin .notice-warning,
			.wp-powersuite-admin .notice-info,
			.wp-powersuite-admin .notice-success {
				display: block !important;
			}';
	}

	/**
	 * @return array<string, bool>
	 */
	private function getSettings(): array {
		if ( $this->settings_cache !== null ) {
			return $this->settings_cache;
		}

		$settings = $this->moduleSettings()->all( [] );
		$settings = is_array( $settings ) ? $settings : [];

		$this->settings_cache = self::normalizeSettings( $settings );
		return $this->settings_cache;
	}
}
