<?php

defined( 'ABSPATH' ) || exit;

/**
 * Dashboard module.
 *
 * Two related features for the wp-admin Dashboard screen:
 *  1. Selectively remove WordPress's default dashboard widgets (WP News,
 *     Quick Draft, At a Glance, Activity, Site Health, and the dismissible
 *     "Welcome to WordPress" panel).
 *  2. Optionally add a custom branded welcome widget pinned to the top of
 *     the dashboard with a title and rich-text body, scoped to a chosen set
 *     of roles (or all roles when none are selected).
 *
 * The widget body is wp_kses_post-sanitised on save and re-sanitised on
 * output. Pinning to the top is done by reordering the bw_dev_welcome key
 * to the front of $wp_meta_boxes['dashboard']['normal']['core'] — user
 * preferences saved via meta-box-order_dashboard still win on subsequent
 * loads, so editors can rearrange normally.
 *
 * Why this exists: every Bowden Works client handoff repeats the same
 * dashboard-tidy + welcome-message task. This module makes it a config.
 *
 * @package BW_Dev
 */

class BW_Dev_Module_Dashboard implements BW_Dev_Module_Interface {

	const WIDGET_ID         = 'bw_dev_welcome';
	const CACHE_OPTION      = 'bw_dev_dashboard_widget_cache';
	const CORE_WIDGET_IDS   = array(
		'dashboard_primary',
		'dashboard_quick_press',
		'dashboard_right_now',
		'dashboard_activity',
		'dashboard_site_health',
		'dashboard_recent_comments', // handled by disable_comments — excluded from plugin-widget list.
		self::WIDGET_ID,             // our own welcome widget — never list ourselves as a "junk" widget.
	);

	public function slug(): string {
		return 'dashboard';
	}

	public function label(): string {
		return __( 'Dashboard', 'bw-dev' );
	}

	public function group(): string {
		return 'editor_admin';
	}

	public function default_settings(): array {
		return array(
			'remove'  => array(
				'wp_news'       => false,
				'quick_draft'   => false,
				'at_a_glance'   => false,
				'activity'      => false,
				'site_health'   => false,
				'welcome_panel' => false,
			),
			// Plugin-added widget IDs the user has chosen to hide. Discovered
			// dynamically via `cache_plugin_widgets()` on each Dashboard load.
			'remove_widgets' => array(),
			'welcome' => array(
				'enabled' => false,
				'title'   => '',
				'content' => '',
				'roles'   => array(),
			),
		);
	}

	public function sanitize( array $data ): array {
		$remove_raw  = isset( $data['remove'] ) && is_array( $data['remove'] ) ? $data['remove'] : array();
		$welcome_raw = isset( $data['welcome'] ) && is_array( $data['welcome'] ) ? $data['welcome'] : array();

		$remove = array(
			'wp_news'       => ! empty( $remove_raw['wp_news'] ),
			'quick_draft'   => ! empty( $remove_raw['quick_draft'] ),
			'at_a_glance'   => ! empty( $remove_raw['at_a_glance'] ),
			'activity'      => ! empty( $remove_raw['activity'] ),
			'site_health'   => ! empty( $remove_raw['site_health'] ),
			'welcome_panel' => ! empty( $remove_raw['welcome_panel'] ),
		);

		$roles_raw = isset( $welcome_raw['roles'] ) && is_array( $welcome_raw['roles'] ) ? $welcome_raw['roles'] : array();
		$roles     = array();
		foreach ( $roles_raw as $role_slug ) {
			$role_slug = sanitize_key( (string) $role_slug );
			if ( '' !== $role_slug ) {
				$roles[] = $role_slug;
			}
		}

		$welcome = array(
			'enabled' => ! empty( $welcome_raw['enabled'] ),
			'title'   => isset( $welcome_raw['title'] ) ? sanitize_text_field( wp_unslash( (string) $welcome_raw['title'] ) ) : '',
			'content' => isset( $welcome_raw['content'] ) ? wp_kses_post( wp_unslash( (string) $welcome_raw['content'] ) ) : '',
			'roles'   => array_values( array_unique( $roles ) ),
		);

		$remove_widgets_raw = isset( $data['remove_widgets'] ) && is_array( $data['remove_widgets'] ) ? $data['remove_widgets'] : array();
		$remove_widgets     = array();
		foreach ( $remove_widgets_raw as $widget_id ) {
			$widget_id = (string) $widget_id;
			// Widget IDs are typically alphanumeric + underscore/dash; cap at 100 chars to be safe.
			$widget_id = preg_replace( '/[^a-zA-Z0-9_\-]/', '', $widget_id );
			$widget_id = substr( $widget_id, 0, 100 );
			if ( '' === $widget_id ) {
				continue;
			}
			$remove_widgets[] = $widget_id;
		}

		return array(
			'remove'         => $remove,
			'remove_widgets' => array_values( array_unique( $remove_widgets ) ),
			'welcome'        => $welcome,
		);
	}

	public function register(): void {
		add_action( 'wp_dashboard_setup', array( $this, 'configure_dashboard' ), 99 );
		add_action( 'admin_init',         array( $this, 'maybe_remove_welcome_panel' ) );
	}

	public function maybe_remove_welcome_panel(): void {
		$remove = bw_dev()->settings()->get( $this->slug(), 'remove', array() );
		if ( is_array( $remove ) && ! empty( $remove['welcome_panel'] ) ) {
			remove_action( 'welcome_panel', 'wp_welcome_panel' );
		}
	}

	public function configure_dashboard(): void {
		// Snapshot the dashboard widgets BEFORE removing any. The snapshot
		// powers the "plugin widgets" checklist on the settings tab. Without
		// caching first, widgets the user has chosen to hide would disappear
		// from $wp_meta_boxes after removal and they'd be unable to untick them.
		$this->cache_plugin_widgets();

		$remove = bw_dev()->settings()->get( $this->slug(), 'remove', array() );
		if ( is_array( $remove ) ) {
			if ( ! empty( $remove['wp_news'] ) ) {
				remove_meta_box( 'dashboard_primary', 'dashboard', 'side' );
			}
			if ( ! empty( $remove['quick_draft'] ) ) {
				remove_meta_box( 'dashboard_quick_press', 'dashboard', 'side' );
			}
			if ( ! empty( $remove['at_a_glance'] ) ) {
				remove_meta_box( 'dashboard_right_now', 'dashboard', 'normal' );
			}
			if ( ! empty( $remove['activity'] ) ) {
				remove_meta_box( 'dashboard_activity', 'dashboard', 'normal' );
			}
			if ( ! empty( $remove['site_health'] ) ) {
				remove_meta_box( 'dashboard_site_health', 'dashboard', 'normal' );
			}
		}

		// Remove user-chosen plugin widgets.
		$remove_plugin_widgets = bw_dev()->settings()->get( $this->slug(), 'remove_widgets', array() );
		if ( is_array( $remove_plugin_widgets ) && ! empty( $remove_plugin_widgets ) ) {
			$cache = $this->load_cache();
			foreach ( $remove_plugin_widgets as $widget_id ) {
				$context = isset( $cache['widgets'][ $widget_id ]['context'] ) ? (string) $cache['widgets'][ $widget_id ]['context'] : 'normal';
				remove_meta_box( $widget_id, 'dashboard', $context );
			}
		}

		$welcome = bw_dev()->settings()->get( $this->slug(), 'welcome', array() );
		if ( is_array( $welcome ) && ! empty( $welcome['enabled'] ) && $this->user_can_see_widget( $welcome ) ) {
			wp_add_dashboard_widget(
				self::WIDGET_ID,
				$this->resolve_widget_title( $welcome ),
				array( $this, 'render_welcome_widget' )
			);
			$this->pin_welcome_to_top();
		}
	}

	private function resolve_widget_title( array $welcome ): string {
		$title = isset( $welcome['title'] ) ? trim( (string) $welcome['title'] ) : '';
		if ( '' === $title ) {
			/* translators: %s: site name */
			$title = sprintf( __( 'Welcome to %s', 'bw-dev' ), get_bloginfo( 'name' ) );
		}
		return $title;
	}

	private function user_can_see_widget( array $welcome ): bool {
		$allowed = isset( $welcome['roles'] ) && is_array( $welcome['roles'] ) ? $welcome['roles'] : array();
		if ( empty( $allowed ) ) {
			return true;
		}
		$user = wp_get_current_user();
		if ( ! ( $user instanceof WP_User ) || 0 === (int) $user->ID ) {
			return false;
		}
		foreach ( (array) $user->roles as $role ) {
			if ( in_array( $role, $allowed, true ) ) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Snapshot every non-core dashboard widget currently registered into the
	 * cache option. Fires inside `wp_dashboard_setup` at priority 99 — after
	 * every plugin's default-priority-10 registration. Excludes WP core
	 * widgets we already handle explicitly, the legacy `dashboard_recent_comments`
	 * widget (covered by disable_comments), and our own welcome widget.
	 */
	private function cache_plugin_widgets(): void {
		global $wp_meta_boxes;
		if ( ! isset( $wp_meta_boxes['dashboard'] ) || ! is_array( $wp_meta_boxes['dashboard'] ) ) {
			return;
		}
		$excluded = array_flip( self::CORE_WIDGET_IDS );
		$found    = array();
		foreach ( $wp_meta_boxes['dashboard'] as $context => $priorities ) {
			$context = (string) $context;
			if ( ! in_array( $context, array( 'normal', 'side', 'advanced' ), true ) ) {
				continue;
			}
			if ( ! is_array( $priorities ) ) {
				continue;
			}
			foreach ( $priorities as $priority => $boxes ) {
				if ( ! is_array( $boxes ) ) {
					continue;
				}
				foreach ( $boxes as $id => $box ) {
					$id = (string) $id;
					if ( isset( $excluded[ $id ] ) ) {
						continue;
					}
					if ( ! is_array( $box ) || empty( $box['title'] ) ) {
						continue;
					}
					$title = wp_strip_all_tags( (string) $box['title'] );
					if ( '' === $title ) {
						$title = $id;
					}
					$found[ $id ] = array(
						'title'   => $title,
						'context' => $context,
					);
				}
			}
		}

		$existing = (array) get_option( self::CACHE_OPTION, array() );
		$payload  = array(
			'updated_at' => time(),
			'widgets'    => $found,
		);
		// Only write if the widget set changed — avoid an option write on every dashboard load.
		$prev_widgets = isset( $existing['widgets'] ) && is_array( $existing['widgets'] ) ? $existing['widgets'] : array();
		if ( $prev_widgets !== $found ) {
			update_option( self::CACHE_OPTION, $payload, false );
		} else {
			// Touch the timestamp on every load so the "last scanned" hint in the
			// settings tab reflects freshness, but don't trigger an autoload cache
			// invalidation when the widget set hasn't actually changed.
			$existing['updated_at'] = time();
			update_option( self::CACHE_OPTION, $existing, false );
		}
	}

	private function load_cache(): array {
		$cache = get_option( self::CACHE_OPTION, array() );
		if ( ! is_array( $cache ) ) {
			return array( 'updated_at' => 0, 'widgets' => array() );
		}
		if ( ! isset( $cache['widgets'] ) || ! is_array( $cache['widgets'] ) ) {
			$cache['widgets'] = array();
		}
		if ( ! isset( $cache['updated_at'] ) ) {
			$cache['updated_at'] = 0;
		}
		return $cache;
	}

	private function pin_welcome_to_top(): void {
		global $wp_meta_boxes;
		if ( ! isset( $wp_meta_boxes['dashboard']['normal']['core'][ self::WIDGET_ID ] ) ) {
			return;
		}
		$widget = $wp_meta_boxes['dashboard']['normal']['core'][ self::WIDGET_ID ];
		unset( $wp_meta_boxes['dashboard']['normal']['core'][ self::WIDGET_ID ] );
		$wp_meta_boxes['dashboard']['normal']['core'] = array_merge(
			array( self::WIDGET_ID => $widget ),
			$wp_meta_boxes['dashboard']['normal']['core']
		);
	}

	public function render_welcome_widget(): void {
		$welcome = bw_dev()->settings()->get( $this->slug(), 'welcome', array() );
		$content = is_array( $welcome ) && isset( $welcome['content'] ) ? (string) $welcome['content'] : '';
		if ( '' === trim( $content ) ) {
			echo '<p>' . esc_html__( 'Welcome — your dashboard is set up.', 'bw-dev' ) . '</p>';
			return;
		}
		echo wp_kses_post( $content );
	}

	public function render_tab(): void {
		$remove  = bw_dev()->settings()->get( $this->slug(), 'remove', array() );
		$welcome = bw_dev()->settings()->get( $this->slug(), 'welcome', array() );
		if ( ! is_array( $remove ) ) {
			$remove = array();
		}
		if ( ! is_array( $welcome ) ) {
			$welcome = array();
		}
		$roles_selected     = isset( $welcome['roles'] ) && is_array( $welcome['roles'] ) ? array_flip( $welcome['roles'] ) : array();
		$all_roles          = wp_roles()->roles;
		$base               = BW_Dev_Settings::OPTION . '[' . $this->slug() . ']';
		$plugin_widget_map  = $this->load_cache();
		$plugin_widget_ids  = isset( $plugin_widget_map['widgets'] ) ? $plugin_widget_map['widgets'] : array();
		$widgets_removed    = bw_dev()->settings()->get( $this->slug(), 'remove_widgets', array() );
		$widgets_removed    = is_array( $widgets_removed ) ? array_flip( $widgets_removed ) : array();
		$cache_updated_at   = isset( $plugin_widget_map['updated_at'] ) ? (int) $plugin_widget_map['updated_at'] : 0;
		?>
		<p class="description">
			<?php esc_html_e( 'Clean up the default WordPress dashboard and optionally pin a branded welcome widget to the top.', 'bw-dev' ); ?>
		</p>

		<h3><?php esc_html_e( 'Remove default widgets', 'bw-dev' ); ?></h3>
		<table class="form-table" role="presentation">
			<tbody>
				<tr>
					<th scope="row"><?php esc_html_e( 'Hide these from the Dashboard', 'bw-dev' ); ?></th>
					<td>
						<fieldset>
							<?php
							$widget_map = array(
								'wp_news'       => __( 'WordPress Events and News', 'bw-dev' ),
								'quick_draft'   => __( 'Quick Draft', 'bw-dev' ),
								'at_a_glance'   => __( 'At a Glance', 'bw-dev' ),
								'activity'      => __( 'Activity', 'bw-dev' ),
								'site_health'   => __( 'Site Health Status', 'bw-dev' ),
								'welcome_panel' => __( '"Welcome to WordPress" panel (the dismissible banner at the top)', 'bw-dev' ),
							);
							foreach ( $widget_map as $key => $label ) :
								$field_name = $base . '[remove][' . $key . ']';
								$checked    = ! empty( $remove[ $key ] );
								?>
								<label style="display:block;margin:4px 0;">
									<input type="checkbox" name="<?php echo esc_attr( $field_name ); ?>" value="1" <?php checked( $checked ); ?> />
									<?php echo esc_html( $label ); ?>
								</label>
							<?php endforeach; ?>
						</fieldset>
					</td>
				</tr>
			</tbody>
		</table>

		<h3><?php esc_html_e( 'Plugin-added widgets', 'bw-dev' ); ?></h3>
		<p class="description">
			<?php
			if ( empty( $plugin_widget_ids ) ) {
				esc_html_e( 'No plugin-added dashboard widgets have been detected yet. Visit the Dashboard at least once after activating new plugins, then come back here — every widget the plugins register will appear in this list.', 'bw-dev' );
			} else {
				printf(
					/* translators: 1: count of detected widgets, 2: site-tz timestamp */
					esc_html__( '%1$d plugin-added widget(s) detected. Last scan: %2$s.', 'bw-dev' ),
					count( $plugin_widget_ids ),
					$cache_updated_at > 0 ? '<code>' . esc_html( wp_date( 'Y-m-d H:i', $cache_updated_at ) ) . '</code>' : esc_html__( 'never', 'bw-dev' )
				);
			}
			?>
		</p>
		<?php if ( ! empty( $plugin_widget_ids ) ) : ?>
			<table class="form-table" role="presentation">
				<tbody>
					<tr>
						<th scope="row"><?php esc_html_e( 'Hide these widgets', 'bw-dev' ); ?></th>
						<td>
							<fieldset style="display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:6px 18px;">
								<?php foreach ( $plugin_widget_ids as $widget_id => $info ) :
									$title   = isset( $info['title'] ) ? (string) $info['title'] : (string) $widget_id;
									$context = isset( $info['context'] ) ? (string) $info['context'] : '';
									$checked = isset( $widgets_removed[ $widget_id ] );
									?>
									<label style="display:flex;align-items:flex-start;gap:6px;">
										<input type="checkbox" name="<?php echo esc_attr( $base . '[remove_widgets][]' ); ?>" value="<?php echo esc_attr( $widget_id ); ?>" <?php checked( $checked ); ?> />
										<span>
											<?php echo esc_html( $title ); ?>
											<br /><code style="color:#646970;font-size:11px;"><?php echo esc_html( $widget_id ); ?><?php if ( '' !== $context ) : ?> · <?php echo esc_html( $context ); ?><?php endif; ?></code>
										</span>
									</label>
								<?php endforeach; ?>
							</fieldset>
							<p class="description" style="margin-top:10px;">
								<a href="<?php echo esc_url( admin_url( 'index.php' ) ); ?>"><?php esc_html_e( 'Open Dashboard to refresh the list →', 'bw-dev' ); ?></a>
								<?php esc_html_e( '(visit the Dashboard after activating or removing plugins to update what shows here)', 'bw-dev' ); ?>
							</p>
						</td>
					</tr>
				</tbody>
			</table>
		<?php endif; ?>

		<h3><?php esc_html_e( 'Welcome widget', 'bw-dev' ); ?></h3>
		<table class="form-table" role="presentation">
			<tbody>
				<tr>
					<th scope="row"><?php esc_html_e( 'Pin a welcome widget', 'bw-dev' ); ?></th>
					<td>
						<label>
							<input type="checkbox" name="<?php echo esc_attr( $base . '[welcome][enabled]' ); ?>" value="1" <?php checked( ! empty( $welcome['enabled'] ) ); ?> />
							<?php esc_html_e( 'Show a custom welcome widget at the top of the Dashboard', 'bw-dev' ); ?>
						</label>
					</td>
				</tr>
				<tr>
					<th scope="row"><label for="bw-dev-dashboard-welcome-title"><?php esc_html_e( 'Widget title', 'bw-dev' ); ?></label></th>
					<td>
						<input type="text" id="bw-dev-dashboard-welcome-title" name="<?php echo esc_attr( $base . '[welcome][title]' ); ?>" value="<?php echo esc_attr( (string) ( $welcome['title'] ?? '' ) ); ?>" class="regular-text" placeholder="<?php /* translators: %s: site name */ echo esc_attr( sprintf( __( 'Welcome to %s', 'bw-dev' ), get_bloginfo( 'name' ) ) ); ?>" />
						<p class="description"><?php esc_html_e( 'Empty = "Welcome to <site name>".', 'bw-dev' ); ?></p>
					</td>
				</tr>
				<tr>
					<th scope="row"><label for="bw-dev-dashboard-welcome-content"><?php esc_html_e( 'Widget body', 'bw-dev' ); ?></label></th>
					<td>
						<?php
						wp_editor(
							(string) ( $welcome['content'] ?? '' ),
							'bw-dev-dashboard-welcome-content',
							array(
								'textarea_name' => $base . '[welcome][content]',
								'media_buttons' => false,
								'textarea_rows' => 8,
								'teeny'         => true,
							)
						);
						?>
						<p class="description"><?php esc_html_e( 'Plain HTML and links are allowed. Saved through wp_kses_post.', 'bw-dev' ); ?></p>
					</td>
				</tr>
				<tr>
					<th scope="row"><?php esc_html_e( 'Show only to these roles', 'bw-dev' ); ?></th>
					<td>
						<fieldset>
							<?php foreach ( $all_roles as $role_slug => $role_data ) :
								$role_label = isset( $role_data['name'] ) ? translate_user_role( (string) $role_data['name'] ) : (string) $role_slug;
								$field_name = $base . '[welcome][roles][]';
								$is_checked = isset( $roles_selected[ $role_slug ] );
								?>
								<label style="display:inline-block;margin-right:14px;">
									<input type="checkbox" name="<?php echo esc_attr( $field_name ); ?>" value="<?php echo esc_attr( $role_slug ); ?>" <?php checked( $is_checked ); ?> />
									<?php echo esc_html( $role_label ); ?>
								</label>
							<?php endforeach; ?>
						</fieldset>
						<p class="description"><?php esc_html_e( 'Leave all unchecked to show to every role.', 'bw-dev' ); ?></p>
					</td>
				</tr>
			</tbody>
		</table>

		<h3><?php esc_html_e( 'Notes', 'bw-dev' ); ?></h3>
		<ul style="list-style:disc;margin-left:20px;">
			<li><?php esc_html_e( 'Users can drag dashboard widgets to reorder them — those preferences are saved per-user and override the default pin-to-top placement on subsequent loads.', 'bw-dev' ); ?></li>
			<li><?php esc_html_e( 'The Recent Comments widget is handled by the Disable Comments module — enable that instead of relying on this list.', 'bw-dev' ); ?></li>
			<li><?php esc_html_e( 'The dismissible "Welcome to WordPress" panel is a separate hook from the widgets; tick the checkbox above to suppress it.', 'bw-dev' ); ?></li>
		</ul>
		<?php
	}

	public function uninstall(): void {
		// Settings live under bw_dev_settings — root option drop covers them.
	}
}
