<?php

namespace DisableFileEditing;

use WP_PowerSuite\Core\AbstractModule;

class Module extends AbstractModule {

	private const MODULE_ID = 'disable-file-editing';
	private const MODULE_VERSION = '1.2.0';

	/** Late priority so another plugin cannot re-grant file editor caps after this runs. */
	private const FILTER_PRIORITY_LATE = \PHP_INT_MAX;

	private bool $block_theme_editor = false;
	private bool $block_plugin_editor = false;

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

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

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

	public function getDescription(): string {
		return __( 'Remove the dashboard screens that let anyone edit theme or plugin code from the browser—one less disaster if an account is compromised.', 'wp-powersuite' );
	}

	public function getContext(): string|array {
		// admin-ajax.php uses the AJAX file editor; protection must load there too.
		return [ 'admin', 'ajax' ];
	}

	protected function registerHooks(): void {
		$settings     = $this->getSettings();
		$block_theme  = ! empty( $settings['disable_theme_editor'] );
		$block_plugin = ! empty( $settings['disable_plugin_editor'] );

		if ( ! $block_theme && ! $block_plugin ) {
			return;
		}

		$this->block_theme_editor  = $block_theme;
		$this->block_plugin_editor = $block_plugin;

		// When both editors are disabled, prefer core's constant so third-party checks of DISALLOW_FILE_EDIT work.
		if ( $block_theme && $block_plugin && ! defined( 'DISALLOW_FILE_EDIT' ) ) {
			define( 'DISALLOW_FILE_EDIT', true );
		}

		add_action( 'admin_init', [ $this, 'blockDirectEditorAccess' ], 1 );

		if ( defined( 'DISALLOW_FILE_EDIT' ) && DISALLOW_FILE_EDIT ) {
			return;
		}

		add_filter( 'map_meta_cap', [ $this, 'filterMapMetaCap' ], self::FILTER_PRIORITY_LATE, 4 );
	}

	public function getDefaultSettings(): array {
		return [
			'disable_theme_editor'  => true,
			'disable_plugin_editor' => true,
		];
	}

	/**
	 * Merge stored settings with defaults so explicit false values persist.
	 *
	 * @param array<string, mixed> $stored Raw settings from OptionStore.
	 * @return array{disable_theme_editor: bool, disable_plugin_editor: bool}
	 */
	public static function normalizeSettings( array $stored ): array {
		$defaults = [
			'disable_theme_editor'  => true,
			'disable_plugin_editor' => true,
		];

		$resolved = $defaults;
		foreach ( $defaults as $key => $default_value ) {
			if ( array_key_exists( $key, $stored ) ) {
				$resolved[ $key ] = (bool) $stored[ $key ];
			}
		}

		return $resolved;
	}

	/**
	 * @param array<string, mixed> $settings Unsanitized settings payload.
	 * @return array{disable_theme_editor: bool, disable_plugin_editor: bool}
	 */
	public static function sanitizeSettings( array $settings ): array {
		return [
			'disable_theme_editor'  => ! empty( $settings['disable_theme_editor'] ),
			'disable_plugin_editor' => ! empty( $settings['disable_plugin_editor'] ),
		];
	}

	/**
	 * Whether any editor blocking is configured (ignores wp-config overrides).
	 *
	 * @param array<string, mixed> $stored Raw or normalized settings.
	 */
	public static function isProtectionConfigured( array $stored ): bool {
		$settings = self::normalizeSettings( $stored );

		return ! empty( $settings['disable_theme_editor'] ) || ! empty( $settings['disable_plugin_editor'] );
	}

	private function getSettings(): array {
		$stored = $this->moduleSettings()->all( [] );

		return self::normalizeSettings( is_array( $stored ) ? $stored : [] );
	}

	/**
	 * Block direct admin screen access even if menus are hidden or caps are re-granted.
	 */
	public function blockDirectEditorAccess(): void {
		global $pagenow;

		if ( ! is_string( $pagenow ) ) {
			return;
		}

		if ( $this->block_theme_editor && in_array( $pagenow, [ 'theme-editor.php', 'site-editor.php' ], true ) ) {
			wp_die(
				esc_html__( 'Theme file editing has been disabled.', 'wp-powersuite' ),
				esc_html__( 'File editing disabled', 'wp-powersuite' ),
				[ 'response' => 403 ]
			);
		}

		if ( $this->block_plugin_editor && $pagenow === 'plugin-editor.php' ) {
			wp_die(
				esc_html__( 'Plugin file editing has been disabled.', 'wp-powersuite' ),
				esc_html__( 'File editing disabled', 'wp-powersuite' ),
				[ 'response' => 403 ]
			);
		}
	}

	/**
	 * @param string[] $caps
	 * @param mixed[]  $args
	 * @return string[]
	 */
	public function filterMapMetaCap( array $caps, string $cap, int $user_id, array $args ): array {
		if ( 'edit_themes' === $cap && $this->block_theme_editor ) {
			return [ 'do_not_allow' ];
		}

		if ( 'edit_plugins' === $cap && $this->block_plugin_editor ) {
			return [ 'do_not_allow' ];
		}

		// Legacy cap grouped with theme/plugin editors in core; block when both editors are disabled.
		if ( 'edit_files' === $cap && $this->block_theme_editor && $this->block_plugin_editor ) {
			return [ 'do_not_allow' ];
		}

		return $caps;
	}
}
