<?php
/**
 * Auto Image Upload Rename Module
 *
 * Automatically renames uploaded image filenames: lowercase, hyphens for special chars,
 * collapsed hyphens, and a 3-character unique ID to prevent collisions.
 *
 * @package AutoImageUploadRename
 * @since 1.0.0
 */

namespace AutoImageUploadRename;

use WP_PowerSuite\Core\AbstractModule;
use WP_PowerSuite\Core\OptionStoreModuleSettingsRepository;

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

class Module extends AbstractModule {

	private const MODULE_ID = 'auto-image-upload-rename';
	private const MODULE_VERSION = '1.0.0';
	private const IMAGE_EXTENSIONS = [
		'jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'bmp', 'ico', 'tiff', 'tif',
	];
	private const MAX_BASE_LENGTH = 180;
	private const UNIQUE_ID_LENGTH = 3;
	private const UNIQUE_ID_CHARS = 'abcdefghjkmnpqrstuvwxyz23456789';

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

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

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

	public function getDescription(): string {
		return __( 'Automatically clean uploaded image filenames by converting to lowercase, removing special characters, and adding a unique identifier. No configuration needed.', 'wp-powersuite' );
	}

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

	protected function registerHooks(): void {
		if ( OptionStoreModuleSettingsRepository::isModuleEnabled( 'media-rename-manager' ) ) {
			return;
		}

		add_filter( 'wp_handle_upload_prefilter', [ $this, 'renameUploadedImage' ], 10, 1 );
	}

	/**
	 * Rename image uploads to a clean filename.
	 *
	 * @param array<string, mixed> $file File array from $_FILES.
	 * @return array<string, mixed>
	 */
	public function renameUploadedImage( array $file ): array {
		if ( isset( $file['error'] ) && UPLOAD_ERR_OK !== (int) $file['error'] ) {
			return $file;
		}

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

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

		$new_name = $this->buildCleanFilename( isset( $file['name'] ) ? (string) $file['name'] : '' );
		if ( $new_name !== '' ) {
			$file['name'] = $new_name;
		}

		return $file;
	}

	/**
	 * Whether the upload is a supported raster image (extension whitelist only).
	 *
	 * @param array<string, mixed> $file File with 'name' key.
	 */
	private function isImageFile( array $file ): bool {
		$name = isset( $file['name'] ) ? (string) $file['name'] : '';
		$name = basename( str_replace( "\0", '', $name ) );
		if ( $name === '' ) {
			return false;
		}

		$ext = strtolower( pathinfo( $name, PATHINFO_EXTENSION ) );
		return $ext !== '' && in_array( $ext, self::IMAGE_EXTENSIONS, true );
	}

	/**
	 * Verify uploaded bytes match a known image signature.
	 */
	private function validateImageContent( string $path ): bool {
		if ( ! is_uploaded_file( $path ) || ! is_readable( $path ) ) {
			return false;
		}

		if ( function_exists( 'exif_imagetype' ) ) {
			return exif_imagetype( $path ) !== false;
		}

		return $this->validateImageMagicBytes( $path );
	}

	/**
	 * Fallback signature check when the exif extension is unavailable.
	 */
	private function validateImageMagicBytes( string $path ): bool {
		$handle = fopen( $path, 'rb' );
		if ( $handle === false ) {
			return false;
		}

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

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

		if ( strncmp( $header, "\xFF\xD8\xFF", 3 ) === 0 ) {
			return true;
		}
		if ( strncmp( $header, "\x89PNG\r\n\x1a\n", 8 ) === 0 ) {
			return true;
		}
		if ( strncmp( $header, 'GIF87a', 6 ) === 0 || strncmp( $header, 'GIF89a', 6 ) === 0 ) {
			return true;
		}
		if ( strncmp( $header, 'RIFF', 4 ) === 0 && strlen( $header ) >= 12 && substr( $header, 8, 4 ) === 'WEBP' ) {
			return true;
		}
		if ( strlen( $header ) >= 12 && substr( $header, 4, 4 ) === 'ftyp' ) {
			return strpos( $header, 'avif' ) !== false || strpos( $header, 'avis' ) !== false;
		}
		if ( strncmp( $header, 'BM', 2 ) === 0 ) {
			return true;
		}
		if ( strncmp( $header, "\0\0\x01\0", 4 ) === 0 ) {
			return true;
		}
		if ( strncmp( $header, 'II', 2 ) === 0 || strncmp( $header, 'MM', 2 ) === 0 ) {
			return true;
		}

		return false;
	}

	/**
	 * Build a clean filename: lowercase, hyphens, collapsed, plus unique ID.
	 */
	private function buildCleanFilename( string $original_name ): string {
		$original_name = basename( str_replace( "\0", '', trim( $original_name ) ) );
		if ( $original_name === '' ) {
			return '';
		}

		$ext  = strtolower( pathinfo( $original_name, PATHINFO_EXTENSION ) );
		$base = pathinfo( $original_name, PATHINFO_FILENAME );

		if ( $ext !== '' && ! in_array( $ext, self::IMAGE_EXTENSIONS, true ) ) {
			$ext = '';
		}

		if ( $base === '' ) {
			$base = 'image';
		}

		$base = strtolower( $base );
		$base = preg_replace( '/[^a-z0-9]+/', '-', $base );
		if ( ! is_string( $base ) || $base === '' ) {
			$base = 'image';
		} else {
			$base = trim( preg_replace( '/-+/', '-', trim( $base, '-' ) ), '-' );
			if ( $base === '' ) {
				$base = 'image';
			}
		}

		if ( strlen( $base ) > self::MAX_BASE_LENGTH ) {
			$base = rtrim( substr( $base, 0, self::MAX_BASE_LENGTH ), '-' );
			if ( $base === '' ) {
				$base = 'image';
			}
		}

		$unique = $this->generateUniqueId();

		if ( $ext !== '' ) {
			return $base . '-' . $unique . '.' . $ext;
		}

		return $base . '-' . $unique;
	}

	private function generateUniqueId(): string {
		$len   = strlen( self::UNIQUE_ID_CHARS );
		$max   = $len - 1;
		$chars = self::UNIQUE_ID_CHARS;
		$id    = '';

		for ( $i = 0; $i < self::UNIQUE_ID_LENGTH; $i++ ) {
			$id .= $chars[ random_int( 0, $max ) ];
		}

		return $id;
	}
}
