<?php
/**
 * Disable Comments Module
 *
 * @package DisableComments
 * @since 1.0.0
 */

namespace DisableComments;

use WP_Admin_Bar;
use WP_Error;
use WP_PowerSuite\Core\AbstractModule;

class Module extends AbstractModule {

	private const MODULE_ID = 'disable-comments';
	private const MODULE_VERSION = '1.2.0';

	private ?array $settings_cache = null;

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

	public function getName(): string {
		return __( 'Disable Comments', 'wp-powersuite' );
	}

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

	public function getDescription(): string {
		return __( 'Comments off everywhere—cleaner for brochure sites, landing pages, or catalogs.', 'wp-powersuite' );
	}

	public function getContext(): string {
		return 'both';
	}

	/**
	 * Whether commenting is disabled for a post type.
	 *
	 * Other modules (e.g. CAPTCHA) can call this via the loaded module instance or
	 * the {@see 'wp_powersuite_disable_comments_is_disabled_for_post_type'} filter.
	 */
	public function isCommentingDisabledForPostType( string $post_type ): bool {
		if ( $post_type === '' || ! post_type_exists( $post_type ) ) {
			return false;
		}

		$disabled = in_array( $post_type, $this->getDisabledPostTypes(), true );

		/**
		 * Filter whether commenting is disabled for a post type.
		 *
		 * @param bool   $disabled  Whether commenting is disabled.
		 * @param string $post_type Post type slug.
		 */
		return (bool) apply_filters( 'wp_powersuite_disable_comments_is_disabled_for_post_type', $disabled, $post_type );
	}

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

		add_filter( 'comments_open', [ $this, 'disableCommentsForPostType' ], 999, 2 );
		add_filter( 'pings_open', [ $this, 'disableCommentsForPostType' ], 999, 2 );
		add_filter( 'comments_array', [ $this, 'closeExistingComments' ], 999, 2 );
		add_filter( 'get_comments_number', [ $this, 'getCommentsNumber' ], 999, 2 );

		add_action( 'admin_init', [ $this, 'disableCommentsAdminEdit' ] );
		add_action( 'template_redirect', [ $this, 'maybeShowBlankCommentTemplate' ] );

		add_filter( 'rest_pre_insert_comment', [ $this, 'blockRestCommentInsert' ], 10, 2 );
		add_filter( 'xmlrpc_methods', [ $this, 'maybeDisableXmlrpcComments' ] );
		add_filter( 'rest_endpoints', [ $this, 'maybeDisableRestCommentEndpoints' ] );

		add_action( 'admin_bar_menu', [ $this, 'preventCommentsAdminBarMenu' ], 1 );
		add_action( 'admin_bar_menu', [ $this, 'removeCommentsAdminBarNode' ], 999 );
		add_action( 'wp_before_admin_bar_render', [ $this, 'removeCommentsAdminBarBeforeRender' ] );

		$settings = $this->getSettings();

		if ( ! empty( $settings['remove_menu'] ) || $this->areCommentsDisabledOnAllCapableTypes() ) {
			add_action( 'admin_menu', [ $this, 'removeCommentsMenu' ] );
			add_action( 'admin_init', [ $this, 'redirectCommentsPage' ] );
		}

		if ( ! empty( $settings['remove_dashboard_widget'] ) ) {
			add_action( 'admin_init', [ $this, 'removeCommentsMetabox' ] );
		}

		if ( ! empty( $settings['remove_comments_column'] ) ) {
			foreach ( $this->getDisabledPostTypes() as $post_type ) {
				if ( ! is_string( $post_type ) || ! post_type_exists( $post_type ) ) {
					continue;
				}

				$hook = $post_type === 'post'
					? 'manage_posts_columns'
					: ( $post_type === 'page' ? 'manage_pages_columns' : "manage_{$post_type}_posts_columns" );

				add_filter( $hook, [ $this, 'removeCommentsColumn' ] );
			}
		}
	}

	public function disableCommentsForPostType( bool $open, int $post_id ): bool {
		if ( ! $open ) {
			return $open;
		}

		return $this->isCommentingDisabledForPostType( $this->resolvePostType( $post_id ) ) ? false : $open;
	}

	public function closeExistingComments( array $comments, int $post_id ): array {
		return $this->isCommentingDisabledForPostType( $this->resolvePostType( $post_id ) ) ? [] : $comments;
	}

	public function getCommentsNumber( int $count, int $post_id ): int {
		return $this->isCommentingDisabledForPostType( $this->resolvePostType( $post_id ) ) ? 0 : $count;
	}

	public function disableCommentsAdminEdit(): void {
		foreach ( $this->getDisabledPostTypes() as $post_type ) {
			if ( ! is_string( $post_type ) || ! post_type_exists( $post_type ) ) {
				continue;
			}

			remove_post_type_support( $post_type, 'comments' );
			remove_post_type_support( $post_type, 'trackbacks' );

			foreach ( [ 'normal', 'side' ] as $context ) {
				remove_meta_box( 'commentstatusdiv', $post_type, $context );
				remove_meta_box( 'commentsdiv', $post_type, $context );
				remove_meta_box( 'trackbacksdiv', $post_type, $context );
			}
		}

		wp_dequeue_script( 'admin-comments' );
	}

	public function maybeShowBlankCommentTemplate(): void {
		if ( ! is_singular() ) {
			return;
		}

		$post_type = get_post_type();
		if ( ! is_string( $post_type ) || ! $this->isCommentingDisabledForPostType( $post_type ) ) {
			return;
		}

		add_filter( 'comments_template', [ $this, 'loadBlankCommentTemplate' ], 20 );
	}

	public function loadBlankCommentTemplate( string $template ): string {
		return __DIR__ . '/includes/blank-comment-template.php';
	}

	/**
	 * @param array<string, mixed>|\WP_Error|null $prepared_comment
	 * @return array<string, mixed>|WP_Error|null
	 */
	public function blockRestCommentInsert( $prepared_comment, $request ) {
		if ( ! $request instanceof \WP_REST_Request ) {
			return $prepared_comment;
		}

		$post_id = (int) $request->get_param( 'post' );
		if ( $post_id <= 0 ) {
			return $prepared_comment;
		}

		if ( $this->isCommentingDisabledForPostType( $this->resolvePostType( $post_id ) ) ) {
			return new WP_Error(
				'rest_comment_disabled',
				__( 'Comments are closed for this post type.', 'wp-powersuite' ),
				[ 'status' => 403 ]
			);
		}

		return $prepared_comment;
	}

	/**
	 * @param array<string, callable> $methods
	 * @return array<string, callable>
	 */
	public function maybeDisableXmlrpcComments( array $methods ): array {
		if ( ! $this->areCommentsDisabledOnAllCapableTypes() ) {
			return $methods;
		}

		unset( $methods['wp.newComment'] );

		return $methods;
	}

	/**
	 * @param array<string, mixed> $endpoints
	 * @return array<string, mixed>
	 */
	public function maybeDisableRestCommentEndpoints( array $endpoints ): array {
		if ( ! $this->areCommentsDisabledOnAllCapableTypes() ) {
			return $endpoints;
		}

		unset( $endpoints['comments'] );
		unset( $endpoints['/wp/v2/comments'] );
		unset( $endpoints['/wp/v2/comments/(?P<id>[\d]+)'] );

		return $endpoints;
	}

	public function redirectCommentsPage(): void {
		global $pagenow;

		if ( $pagenow === 'edit-comments.php' ) {
			wp_safe_redirect( admin_url() );
			exit;
		}
	}

	public function removeCommentsMenu(): void {
		remove_menu_page( 'edit-comments.php' );
	}

	public function removeCommentsMetabox(): void {
		remove_meta_box( 'dashboard_recent_comments', 'dashboard', 'normal' );
	}

	public function preventCommentsAdminBarMenu(): void {
		remove_action( 'admin_bar_menu', 'wp_admin_bar_comments_menu', 60 );
	}

	public function removeCommentsAdminBarNode( WP_Admin_Bar $wp_admin_bar ): void {
		remove_action( 'admin_bar_menu', 'wp_admin_bar_comments_menu', 60 );
		$wp_admin_bar->remove_node( 'comments' );
	}

	public function removeCommentsAdminBarBeforeRender(): void {
		global $wp_admin_bar;

		if ( ! $wp_admin_bar instanceof WP_Admin_Bar ) {
			return;
		}

		remove_action( 'admin_bar_menu', 'wp_admin_bar_comments_menu', 60 );
		$wp_admin_bar->remove_node( 'comments' );
	}

	/**
	 * @param array<string, string> $columns
	 * @return array<string, string>
	 */
	public function removeCommentsColumn( array $columns ): array {
		unset( $columns['comments'] );

		return $columns;
	}

	private function resolvePostType( int $post_id ): string {
		$post_type = get_post_type( $post_id );
		if ( is_string( $post_type ) && $post_type !== '' ) {
			return $post_type;
		}

		global $post;
		if ( $post instanceof \WP_Post && isset( $post->post_type ) ) {
			return (string) $post->post_type;
		}

		return '';
	}

	/**
	 * @return string[]
	 */
	private function getDisabledPostTypes(): array {
		return $this->getSettings()['disabled_post_types'];
	}

	/**
	 * Public post types that can have comments (stable list, not runtime support).
	 *
	 * @return string[]
	 */
	private function getCommentCapablePostTypes(): array {
		$excluded = [
			'attachment',
			'revision',
			'nav_menu_item',
			'custom_css',
			'customize_changeset',
			'oembed_cache',
			'user_request',
			'wp_block',
		];

		$post_types = [];

		foreach ( get_post_types( [ 'public' => true ], 'names' ) as $post_type ) {
			if ( is_string( $post_type ) && ! in_array( $post_type, $excluded, true ) ) {
				$post_types[] = $post_type;
			}
		}

		return $post_types;
	}

	private function areCommentsDisabledOnAllCapableTypes(): bool {
		$capable = $this->getCommentCapablePostTypes();
		if ( empty( $capable ) ) {
			return false;
		}

		$disabled = $this->getDisabledPostTypes();

		return empty( array_diff( $capable, $disabled ) );
	}

	/**
	 * @return array{
	 *     disabled_post_types: string[],
	 *     remove_menu: bool,
	 *     remove_dashboard_widget: bool,
	 *     remove_comments_column: bool
	 * }
	 */
	private function getSettings(): array {
		if ( $this->settings_cache !== null ) {
			return $this->settings_cache;
		}

		$settings = $this->moduleSettings()->all( [] );
		if ( ! is_array( $settings ) ) {
			$settings = [];
		}

		$defaults = [
			'disabled_post_types'     => [ 'post', 'page' ],
			'remove_menu'             => true,
			'remove_dashboard_widget' => true,
			'remove_comments_column'  => true,
		];

		if ( empty( $settings ) ) {
			$this->settings_cache = $defaults;
			return $this->settings_cache;
		}

		if ( array_key_exists( 'disabled_post_types', $settings ) && is_array( $settings['disabled_post_types'] ) ) {
			$disabled_post_types = array_values(
				array_filter(
					array_map( 'sanitize_key', $settings['disabled_post_types'] ),
					static fn( string $post_type ): bool => $post_type !== ''
				)
			);
		} else {
			$disabled_post_types = [ 'post', 'page' ];
		}

		$this->settings_cache = [
			'disabled_post_types'     => $disabled_post_types,
			'remove_menu'             => array_key_exists( 'remove_menu', $settings )
				? ! empty( $settings['remove_menu'] )
				: true,
			'remove_dashboard_widget' => array_key_exists( 'remove_dashboard_widget', $settings )
				? ! empty( $settings['remove_dashboard_widget'] )
				: true,
			'remove_comments_column'  => array_key_exists( 'remove_comments_column', $settings )
				? ! empty( $settings['remove_comments_column'] )
				: true,
		];

		return $this->settings_cache;
	}
}
