<?php
/**
 * Enable AVIF image upload support in WordPress.
 *
 * @package AvifUpload
 */

namespace AvifUpload;

use WP_PowerSuite\Core\AbstractModule;

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

class Module extends AbstractModule {

	private const MODULE_ID = 'avif-upload';

	private const MODULE_VERSION = '1.0.0';

	/** @var bool|null Per-request cache for serverSupportsAvif(). */
	private static ?bool $server_supports_cache = null;

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

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

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

	public function getDescription(): string {
		return __( 'Allow AVIF image uploads in the Media Library. Requires GD or Imagick with AVIF support on the server.', 'wp-powersuite' );
	}

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

	protected function registerHooks(): void {
		if ( is_admin() ) {
			add_action( 'admin_notices', [ $this, 'maybeAdminNotice' ] );
		}

		if ( ! $this->serverSupportsAvif() ) {
			return;
		}

		add_filter( 'upload_mimes', [ $this, 'allowAvifMime' ] );
		add_filter( 'wp_check_filetype_and_ext', [ $this, 'fixFiletypeAndExt' ], 10, 4 );
		add_filter( 'wp_handle_upload_prefilter', [ $this, 'validateUpload' ] );
		add_filter( 'rest_pre_upload_file', [ $this, 'validateRestUpload' ], 10, 2 );

		if ( is_admin() || wp_doing_ajax() ) {
			add_filter( 'wp_prepare_attachment_for_js', [ $this, 'avifPreviewSizes' ] );
			add_filter( 'intermediate_image_sizes_advanced', [ $this, 'noSubsizesForAvif' ], 10, 3 );
		}
	}

	public function serverSupportsAvif(): bool {
		if ( self::$server_supports_cache !== null ) {
			return self::$server_supports_cache;
		}

		if ( function_exists( 'imageavif' ) ) {
			self::$server_supports_cache = true;
			return true;
		}

		if ( class_exists( 'Imagick' ) ) {
			$formats = \Imagick::queryFormats();
			self::$server_supports_cache = is_array( $formats ) && in_array( 'AVIF', $formats, true );
			return self::$server_supports_cache;
		}

		self::$server_supports_cache = false;
		return false;
	}

	public function getCapabilityMessage(): string {
		if ( function_exists( 'imageavif' ) ) {
			return __( 'GD Library with AVIF support (imageavif) is available.', 'wp-powersuite' );
		}
		if ( class_exists( 'Imagick' ) ) {
			$formats = \Imagick::queryFormats();
			if ( is_array( $formats ) && in_array( 'AVIF', $formats, true ) ) {
				return __( 'ImageMagick (Imagick) with AVIF support is available.', 'wp-powersuite' );
			}
			return __( 'ImageMagick is available but AVIF format is not supported.', 'wp-powersuite' );
		}
		return __( 'AVIF is not supported. Enable GD with imageavif or Imagick with AVIF on the server.', 'wp-powersuite' );
	}

	public function maybeAdminNotice(): void {
		if ( ! is_admin() || ! $this->isEnabled() || ! \WP_PowerSuite\Core\NetworkScope::canManagePlugin() ) {
			return;
		}

		if ( ! \WP_PowerSuite\Core\AdminMenu::isPowerSuiteAdminScreen() ) {
			$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
			$on_media = $screen instanceof \WP_Screen && in_array( $screen->id, [ 'upload', 'media' ], true );
			if ( ! $on_media ) {
				return;
			}
		}

		if ( $this->serverSupportsAvif() ) {
			return;
		}

		echo '<div class="notice notice-warning is-dismissible"><p>';
		echo esc_html(
			sprintf(
				/* translators: %s: module name */
				__( '%s is on, but this server cannot accept AVIF uploads yet. Enable GD with imageavif or Imagick with AVIF—or turn this module off.', 'wp-powersuite' ),
				$this->getName()
			)
		);
		echo '</p></div>';
	}

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

		$mimes['avif'] = 'image/avif';
		return $mimes;
	}

	/**
	 * @param array{ext?: string, type?: string, proper_filename?: string} $data
	 * @param string                                                       $file
	 * @param string                                                       $filename
	 * @param array<string, string>                                        $mimes
	 * @return array{ext: string, type: string, proper_filename?: string}
	 */
	public function fixFiletypeAndExt( $data, $file, $filename, $mimes ) {
		if ( ! $this->isEnabled() || ! $this->serverSupportsAvif() ) {
			return $data;
		}

		if ( strtolower( pathinfo( $filename, PATHINFO_EXTENSION ) ) !== 'avif' ) {
			return $data;
		}

		if ( is_string( $file ) && $file !== '' && is_readable( $file ) && ! $this->fileLooksLikeAvif( $file ) ) {
			return $data;
		}

		$data['ext']             = 'avif';
		$data['type']            = 'image/avif';
		$data['proper_filename'] = $data['proper_filename'] ?? $filename;

		return $data;
	}

	/**
	 * @param array{name: string, tmp_name?: string, error?: string} $file
	 * @return array{name: string, tmp_name?: string, error?: string}
	 */
	public function validateUpload( $file ): array {
		if ( ! $this->isEnabled() || ! $this->serverSupportsAvif() ) {
			return $file;
		}

		if ( ! $this->uploadLooksLikeAvif( $file ) ) {
			return $file;
		}

		$tmp = isset( $file['tmp_name'] ) ? (string) $file['tmp_name'] : '';
		if ( $tmp === '' || ! $this->fileLooksLikeAvif( $tmp ) ) {
			$file['error'] = __( 'The uploaded file does not appear to be a valid AVIF image.', 'wp-powersuite' );
		}

		return $file;
	}

	/**
	 * @param array{name?: string, type?: string, tmp_name?: string} $file
	 * @param \WP_REST_Request                                       $request
	 * @return array|\WP_Error
	 */
	public function validateRestUpload( $file, $request ) {
		unset( $request );

		if ( ! $this->isEnabled() || ! $this->serverSupportsAvif() ) {
			return $file;
		}

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

		$tmp = isset( $file['tmp_name'] ) ? (string) $file['tmp_name'] : '';
		if ( $tmp === '' || ! $this->fileLooksLikeAvif( $tmp ) ) {
			return new \WP_Error(
				'avif_upload_invalid',
				__( 'The uploaded file does not appear to be a valid AVIF image.', 'wp-powersuite' ),
				[ 'status' => 400 ]
			);
		}

		return $file;
	}

	/**
	 * @param array{name?: string, type?: string} $file
	 */
	private function uploadLooksLikeAvif( array $file ): bool {
		$name = isset( $file['name'] ) ? (string) $file['name'] : '';
		$type = isset( $file['type'] ) ? (string) $file['type'] : '';
		$ext  = strtolower( pathinfo( $name, PATHINFO_EXTENSION ) );

		return $ext === 'avif' || $type === 'image/avif';
	}

	private function fileLooksLikeAvif( string $path ): bool {
		if ( ! is_file( $path ) || ! is_readable( $path ) ) {
			return false;
		}

		$handle = fopen( $path, 'rb' );
		if ( $handle === false ) {
			return false;
		}

		$header = fread( $handle, 32 );
		fclose( $handle );

		if ( ! is_string( $header ) || strlen( $header ) < 12 ) {
			return false;
		}

		if ( substr( $header, 4, 4 ) !== 'ftyp' ) {
			return false;
		}

		return strpos( $header, 'avif' ) !== false || strpos( $header, 'avis' ) !== false;
	}

	/**
	 * @param array<string, mixed> $response
	 * @return array<string, mixed>
	 */
	public function avifPreviewSizes( $response ): array {
		if ( ! is_array( $response ) || ( $response['mime'] ?? '' ) !== 'image/avif' ) {
			return $response;
		}

		$response['sizes'] = $response['sizes'] ?? [
			'full' => [
				'url'    => $response['url'] ?? '',
				'width'  => null,
				'height' => null,
			],
		];

		return $response;
	}

	/**
	 * Disable intermediate image sizes for AVIF (WordPress cannot reliably generate them).
	 *
	 * @param array<int, array<string, int>> $sizes
	 * @param array<string, mixed>           $metadata
	 * @param int                            $attachment_id
	 * @return array<int, array<string, int>>
	 */
	public function noSubsizesForAvif( $sizes, $metadata, $attachment_id ): array {
		unset( $metadata );

		if ( get_post_mime_type( $attachment_id ) === 'image/avif' ) {
			return [];
		}

		return $sizes;
	}
}
