<?php

namespace GutenbergBlockControl;

use WP_PowerSuite\Core\AbstractModule;

class Module extends AbstractModule {

	private const MODULE_ID = 'gutenberg-block-control';
	private const MODULE_VERSION = '1.2.0';
	private const FILTER_PRIORITY = 999;
	private const BLOCKS_CACHE_KEY = 'wpps_gutenberg_blocks_cache';

	private ?array $settings_cache = null;

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

	public function getName(): string {
		return __( 'Gutenberg Block Control', 'wp-powersuite' );
	}

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

	public function getDescription(): string {
		return __( 'Hide unused or risky blocks so the block editor stays simple and on-brand.', 'wp-powersuite' );
	}

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

	public function getDependencies(): array {
		return [];
	}

	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;
		}

		add_filter( 'allowed_block_types_all', [ $this, 'filterAllowedBlocks' ], self::FILTER_PRIORITY, 2 );
		add_action( 'enqueue_block_editor_assets', [ $this, 'enqueueBlockUnregisterScript' ], 10 );
		add_action( 'enqueue_block_editor_assets', [ $this, 'cacheBlocksOnEditorLoad' ], 1 );
	}

	/**
	 * Enqueue inline script to unregister disabled blocks in the editor.
	 * Provides redundancy alongside the PHP filter for edge cases.
	 */
	public function enqueueBlockUnregisterScript(): void {
		$settings        = $this->getSettings();
		$disabled_blocks = $settings['disabled_blocks'] ?? [];

		if ( empty( $disabled_blocks ) || ! is_array( $disabled_blocks ) ) {
			return;
		}

		$disabled = array_filter( array_map( 'sanitize_text_field', $disabled_blocks ) );
		if ( empty( $disabled ) ) {
			return;
		}

		wp_add_inline_script(
			'wp-blocks',
			'wp.domReady(function(){var d=' . wp_json_encode( array_values( $disabled ) ) . ';d.forEach(function(n){if(wp.blocks.getBlockType(n))wp.blocks.unregisterBlockType(n);});});',
			'after'
		);
	}

	/**
	 * Cache available blocks when block editor loads.
	 * Always rebuilds so plugin blocks (registered on enqueue_block_editor_assets) are included.
	 */
	public function cacheBlocksOnEditorLoad(): void {
		self::buildAndCacheBlocks( self::BLOCKS_CACHE_KEY );
	}

	/**
	 * Transient key used for the block inventory cache.
	 */
	public static function getBlocksCacheKey(): string {
		return self::BLOCKS_CACHE_KEY;
	}

	/**
	 * Build block list and store in transient.
	 *
	 * @param string $cache_key Transient key.
	 * @return array<string, array<int, array<string, string>>> Blocks grouped by category.
	 */
	public static function buildAndCacheBlocks( string $cache_key ): array {
		$block_registry     = \WP_Block_Type_Registry::get_instance();
		$all_blocks         = $block_registry->get_all_registered();
		$blocks_by_category = [];
		$category_labels    = self::getCategoryLabels();

		foreach ( $all_blocks as $block_name => $block_type ) {
			if ( strpos( $block_name, 'core/' ) === false && strpos( $block_name, '/' ) === false ) {
				continue;
			}
			if ( empty( $block_name ) || ! is_string( $block_name ) ) {
				continue;
			}

			$block_title = $block_type->title ?? '';
			if ( empty( $block_title ) || ! is_string( $block_title ) ) {
				$block_title = $block_name;
			}
			if ( empty( $block_title ) ) {
				continue;
			}

			$category       = $block_type->category ?? 'uncategorized';
			$category_label = $category_labels[ $category ] ?? ucfirst( (string) $category );
			if ( ! isset( $blocks_by_category[ $category_label ] ) ) {
				$blocks_by_category[ $category_label ] = [];
			}

			$blocks_by_category[ $category_label ][] = [
				'name'        => $block_name,
				'title'       => $block_title,
				'description' => $block_type->description ?? '',
				'icon'        => $block_type->icon ?? 'block-default',
			];
		}

		foreach ( $blocks_by_category as $cat => $blocks ) {
			usort( $blocks_by_category[ $cat ], fn( $a, $b ) => strcasecmp( $a['title'], $b['title'] ) );
		}

		$cat_order = array_keys( $category_labels );
		uksort(
			$blocks_by_category,
			static function ( $a, $b ) use ( $cat_order, $category_labels ) {
				$a_key = array_search( $a, $category_labels, true );
				$b_key = array_search( $b, $category_labels, true );
				$a_pos = $a_key !== false ? array_search( $a_key, $cat_order, true ) : 999;
				$b_pos = $b_key !== false ? array_search( $b_key, $cat_order, true ) : 999;
				return $a_pos <=> $b_pos;
			}
		);

		set_transient( $cache_key, $blocks_by_category, HOUR_IN_SECONDS );

		return $blocks_by_category;
	}

	/**
	 * Render the block picker list markup (inner content of .wpps-block-control-list).
	 *
	 * @param array<string, array<int, array<string, string>>> $blocks_by_category Blocks grouped by category.
	 * @param array<int, string>                               $disabled_blocks    Block names to mark as disabled.
	 */
	public static function renderBlockListHtml( array $blocks_by_category, array $disabled_blocks ): string {
		ob_start();

		if ( empty( $blocks_by_category ) ) {
			?>
			<div class="wpps-block-control-empty">
				<p>
					<?php esc_html_e( 'No blocks found. Open any post in the block editor first, then return here and click "Refresh Block List".', 'wp-powersuite' ); ?>
				</p>
			</div>
			<?php
		} else {
			foreach ( $blocks_by_category as $category_label => $blocks ) {
				?>
				<div class="wpps-block-category" data-category="<?php echo esc_attr( strtolower( $category_label ) ); ?>">
					<div class="wpps-block-category-header">
						<h3 class="wpps-block-category-title"><?php echo esc_html( $category_label ); ?></h3>
						<div class="wpps-block-category-actions">
							<button type="button" class="button-link wpps-block-category-action" data-action="disable-all"><?php esc_html_e( 'Disable all', 'wp-powersuite' ); ?></button>
							<span class="wpps-block-category-action-sep">|</span>
							<button type="button" class="button-link wpps-block-category-action" data-action="clear"><?php esc_html_e( 'Enable all', 'wp-powersuite' ); ?></button>
						</div>
					</div>
					<div class="wpps-block-category-blocks">
						<?php foreach ( $blocks as $block ) : ?>
							<?php
							$is_disabled = in_array( $block['name'], $disabled_blocks, true );
							$block_id    = 'block-' . str_replace( '/', '-', $block['name'] );
							?>
							<label
								class="wpps-block-item"
								data-block-name="<?php echo esc_attr( $block['name'] ); ?>"
								data-block-title="<?php echo esc_attr( $block['title'] ); ?>"
								<?php if ( ! empty( $block['description'] ) ) : ?>
									title="<?php echo esc_attr( $block['description'] ); ?>"
								<?php endif; ?>
							>
								<input
									type="checkbox"
									name="disabled_blocks[]"
									value="<?php echo esc_attr( $block['name'] ); ?>"
									id="<?php echo esc_attr( $block_id ); ?>"
									<?php checked( $is_disabled ); ?>
									class="wp-powersuite-advanced-setting"
									data-setting-key="disabled_blocks"
								/>
								<span class="wpps-block-title">
									<?php echo esc_html( $block['title'] ); ?>
									<span class="wpps-block-disabled-icon" title="<?php esc_attr_e( 'Disabled', 'wp-powersuite' ); ?>">✕</span>
								</span>
							</label>
						<?php endforeach; ?>
					</div>
				</div>
				<?php
			}
		}

		return (string) ob_get_clean();
	}

	/**
	 * Filter allowed blocks, removing disabled ones from the current allowlist.
	 *
	 * @param bool|string[]              $allowed_block_types    Array of block type slugs, or boolean to enable/disable all.
	 * @param \WP_Block_Editor_Context   $block_editor_context   The current block editor context.
	 * @return string[]|bool Array of allowed block type slugs, or original value if no blocks disabled.
	 */
	public function filterAllowedBlocks( $allowed_block_types, $block_editor_context ) {
		unset( $block_editor_context );

		$settings        = $this->getSettings();
		$disabled_blocks = $settings['disabled_blocks'] ?? [];

		if ( empty( $disabled_blocks ) || ! is_array( $disabled_blocks ) ) {
			return $allowed_block_types;
		}

		$disabled_blocks = array_map( 'sanitize_text_field', $disabled_blocks );
		$disabled_blocks = array_filter(
			$disabled_blocks,
			static function ( $block ) {
				return ! empty( $block ) && is_string( $block );
			}
		);

		if ( empty( $disabled_blocks ) ) {
			return $allowed_block_types;
		}

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

		if ( $allowed_block_types === true || $allowed_block_types === null || ! is_array( $allowed_block_types ) ) {
			$block_registry        = \WP_Block_Type_Registry::get_instance();
			$allowed_block_types   = array_keys( $block_registry->get_all_registered() );
		}

		if ( empty( $allowed_block_types ) ) {
			return $allowed_block_types;
		}

		return array_values( array_diff( $allowed_block_types, $disabled_blocks ) );
	}

	/**
	 * @return array<string, array<int, array<string, string>>>
	 */
	private static function getCategoryLabels(): array {
		return [
			'text'          => __( 'Text', 'wp-powersuite' ),
			'media'         => __( 'Media', 'wp-powersuite' ),
			'design'        => __( 'Design', 'wp-powersuite' ),
			'widgets'       => __( 'Widgets', 'wp-powersuite' ),
			'theme'         => __( 'Theme', 'wp-powersuite' ),
			'embed'         => __( 'Embed', 'wp-powersuite' ),
			'reusable'      => __( 'Reusable Blocks', 'wp-powersuite' ),
			'uncategorized' => __( 'Uncategorized', 'wp-powersuite' ),
		];
	}

	/**
	 * Retrieves module settings from OptionStore (cached after first load).
	 *
	 * @return array<string, mixed> Module settings.
	 */
	private function getSettings(): array {
		if ( $this->settings_cache !== null ) {
			return $this->settings_cache;
		}

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

		$this->settings_cache = [
			'disabled_blocks' => isset( $settings['disabled_blocks'] ) && is_array( $settings['disabled_blocks'] )
				? array_map( 'sanitize_text_field', $settings['disabled_blocks'] )
				: [],
		];

		return $this->settings_cache;
	}
}
