<?php
/**
 * Block video uploads for selected roles.
 *
 * @package BlockVideoUploads
 */

namespace BlockVideoUploads;

use WP_PowerSuite\Core\AbstractModule;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Module extends AbstractModule {

	private const MODULE_ID = 'block-video-uploads';
	private const MODULE_VERSION = '1.0.0';
	private const VIDEO_MIME_PREFIX = 'video/';
	private const VIDEO_EXTENSIONS = [
		'3g2', '3gp', 'avi', 'm4v', 'mkv', 'mov', 'mp4', 'mpeg', 'mpg', 'ogv', 'webm', 'wmv',
	];

	private ?array $blocked_roles_cache = null;

	private ?bool $should_block_cache = null;

	private bool $mime_filter_registered = false;

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

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

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

	public function getDescription(): string {
		return __( 'Blocks all video MIME types at upload time, displaying a friendly message directing users to embed videos instead. Apply to selected user roles via a multiselect.', 'wp-powersuite' );
	}

	public function getContext(): string|array {
		return [ 'admin', 'ajax', 'rest' ];
	}

	protected function registerHooks(): void {
		if ( $this->getBlockedRoles() === [] ) {
			return;
		}

		add_filter( 'wp_handle_upload_prefilter', [ $this, 'blockVideoUpload' ] );
		add_filter( 'rest_pre_upload_file', [ $this, 'blockVideoRestUpload' ], 10, 2 );
		add_filter( 'wp_check_filetype_and_ext', [ $this, 'blockVideoFiletype' ], 10, 4 );

		add_action( 'load-upload.php', [ $this, 'registerMimeFilter' ] );
		add_action( 'load-media.php', [ $this, 'registerMimeFilter' ] );
		add_action( 'wp_enqueue_media', [ $this, 'registerMimeFilter' ] );
	}

	public function registerMimeFilter(): void {
		if ( $this->mime_filter_registered ) {
			return;
		}

		$this->mime_filter_registered = true;
		add_filter( 'upload_mimes', [ $this, 'filterUploadMimes' ] );
	}

	/** @param array<string, string> $mimes */
	public function filterUploadMimes( array $mimes ): array {
		if ( ! $this->shouldBlockCurrentUser() ) {
			return $mimes;
		}

		foreach ( $mimes as $key => $type ) {
			if ( str_starts_with( (string) $type, self::VIDEO_MIME_PREFIX ) ) {
				unset( $mimes[ $key ] );
			}
		}

		return $mimes;
	}

	/** @param array<string, mixed> $file */
	public function blockVideoUpload( array $file ): array {
		if ( ! $this->isVideoFile( $file ) || ! $this->shouldBlockCurrentUser() ) {
			return $file;
		}

		$file['error'] = $this->getBlockedMessage();
		return $file;
	}

	/**
	 * @param array<string, mixed>|\WP_Error $file
	 * @param \WP_REST_Request               $request
	 * @return array<string, mixed>|\WP_Error
	 */
	public function blockVideoRestUpload( $file, $request ) {
		unset( $request );

		if ( ! is_array( $file ) || ! $this->isVideoFile( $file ) || ! $this->shouldBlockCurrentUser() ) {
			return $file;
		}

		return new \WP_Error(
			'video_upload_blocked',
			$this->getBlockedMessage(),
			[ 'status' => 403 ]
		);
	}

	/**
	 * @param array{ext?: string, type?: string, proper_filename?: string|false} $data
	 * @param string                                                              $file
	 * @param string                                                              $filename
	 * @param array<string, string>                                               $mimes
	 * @return array{ext: string, type: string, proper_filename?: string|false}
	 */
	public function blockVideoFiletype( $data, $file, $filename, $mimes ) {
		unset( $file, $mimes );

		if ( ! $this->shouldBlockCurrentUser() || ! is_array( $data ) ) {
			return $data;
		}

		$type = isset( $data['type'] ) ? (string) $data['type'] : '';
		if ( str_starts_with( $type, self::VIDEO_MIME_PREFIX ) ) {
			return [ 'ext' => '', 'type' => '', 'proper_filename' => false ];
		}

		$ext = strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) );
		if ( $ext !== '' && in_array( $ext, self::VIDEO_EXTENSIONS, true ) ) {
			return [ 'ext' => '', 'type' => '', 'proper_filename' => false ];
		}

		return $data;
	}

	/** @param array<string, mixed> $file */
	private function isVideoFile( array $file ): bool {
		$type = isset( $file['type'] ) ? (string) $file['type'] : '';
		if ( str_starts_with( $type, self::VIDEO_MIME_PREFIX ) ) {
			return true;
		}

		$name = isset( $file['name'] ) ? (string) $file['name'] : '';
		$ext  = strtolower( pathinfo( $name, PATHINFO_EXTENSION ) );

		return $ext !== '' && in_array( $ext, self::VIDEO_EXTENSIONS, true );
	}

	private function shouldBlockCurrentUser(): bool {
		if ( $this->should_block_cache !== null ) {
			return $this->should_block_cache;
		}

		if ( ! is_user_logged_in() ) {
			$this->should_block_cache = true;
			return true;
		}

		$user = wp_get_current_user();
		if ( ! $user->exists() || $user->roles === [] ) {
			$this->should_block_cache = false;
			return false;
		}

		$blocked_roles = $this->getBlockedRoles();
		foreach ( $user->roles as $role ) {
			if ( in_array( $role, $blocked_roles, true ) ) {
				$this->should_block_cache = true;
				return true;
			}
		}

		$this->should_block_cache = false;
		return false;
	}

	/** @return list<string> */
	private function getBlockedRoles(): array {
		if ( $this->blocked_roles_cache !== null ) {
			return $this->blocked_roles_cache;
		}

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

		$this->blocked_roles_cache = self::normalizeSettings( $raw )['roles'];

		return $this->blocked_roles_cache;
	}

	/**
	 * @param array<string, mixed> $raw
	 * @return array{roles: list<string>}
	 */
	public static function normalizeSettings( array $raw ): array {
		$roles_raw = $raw['roles'] ?? [];
		$roles     = [];

		if ( is_array( $roles_raw ) ) {
			$roles = array_values(
				array_filter(
					array_map( 'sanitize_key', array_filter( $roles_raw, 'is_string' ) )
				)
			);
		}

		if ( ! function_exists( 'wp_roles' ) ) {
			require_once ABSPATH . 'wp-admin/includes/user.php';
		}

		$roles_obj   = wp_roles();
		$valid_roles = is_object( $roles_obj ) && isset( $roles_obj->roles ) ? array_keys( $roles_obj->roles ) : [];

		if ( $valid_roles !== [] ) {
			$roles = array_values(
				array_filter(
					$roles,
					static fn( string $role ): bool => in_array( $role, $valid_roles, true )
				)
			);
		}

		return [ 'roles' => $roles ];
	}

	private function getBlockedMessage(): string {
		return __( 'Video file uploads are not allowed. Please embed videos instead (e.g. from YouTube or Vimeo).', 'wp-powersuite' );
	}
}
