<?php

namespace MediaRenameManager;

use WP_PowerSuite\Core\AbstractModule;

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

class Module extends AbstractModule {

	private const MODULE_ID      = 'media-rename-manager';
	private const MODULE_VERSION = '1.0.2';

	/**
	 * Extensions that must never be the result of a rename (or the source file).
	 *
	 * @var list<string>
	 */
	private const RESTRICTED_RENAME_EXTENSIONS = [
		'php',
		'php3',
		'php4',
		'php5',
		'php7',
		'php8',
		'phtml',
		'pht',
		'phar',
		'phps',
		'phpt',
		'cgi',
		'fcgi',
		'pl',
		'py',
		'rb',
		'sh',
		'bash',
		'exe',
		'com',
		'bat',
		'cmd',
		'htaccess',
		'htpasswd',
		'ini',
	];

	private ?array $settings_cache = null;

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

	public function getName(): string {
		return __( 'Media Rename Manager', 'wppowersuite-lite' );
	}

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

	public function getDescription(): string {
		return __( 'Rename existing media files in your library with full control. Update SEO-unfriendly camera names, bulk rename product images, and automatically update all references throughout your site.', 'wppowersuite-lite' );
	}

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

	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( 'wp_handle_upload_prefilter', [ $this, 'renameUploadedFile' ] );
		add_action( 'add_meta_boxes_attachment', [ $this, 'addRenameMetaBox' ] );
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueAssets' ] );
		add_action( 'load-upload.php', [ $this, 'registerUploadPageHooks' ] );

		if ( is_admin() || wp_doing_ajax() ) {
			add_filter( 'attachment_fields_to_edit', [ $this, 'addRenameFieldToAttachmentModal' ], 10, 2 );
		}

		add_action( 'wp_ajax_wpps_media_rename_rename', [ $this, 'ajaxRename' ] );
		add_action( 'wp_ajax_wpps_media_rename_scan', [ $this, 'ajaxScanReferences' ] );
		add_action( 'wp_ajax_wpps_media_rename_bulk', [ $this, 'ajaxBulkRename' ] );
		add_action( 'wp_ajax_wpps_media_rename_panel', [ $this, 'ajaxRenamePanel' ] );
	}

	public function registerUploadPageHooks(): void {
		if ( ! current_user_can( 'upload_files' ) ) {
			return;
		}

		add_filter( 'bulk_actions-upload', [ $this, 'addBulkRenameAction' ] );
		add_filter( 'handle_bulk_actions-upload', [ $this, 'handleBulkRenameAction' ], 10, 3 );

		if ( ! isset( $_GET['wpps_bulk_rename'], $_GET['media'] ) ) {
			return;
		}

		add_action( 'admin_notices', [ $this, 'bulkRenameNotice' ] );
		add_action( 'admin_footer-upload.php', [ $this, 'outputBulkRenameModal' ] );
	}

	private function getSettings(): array {
		if ( $this->settings_cache !== null ) {
			return $this->settings_cache;
		}

		$saved    = $this->moduleSettings()->all( [] );
		$defaults = [
			'default_pattern'      => '{title}',
			'case_transformation'  => 'lowercase',
			'max_filename_length'  => 50,
			'excluded_file_types'  => '',
			'add_unique_id_suffix' => false,
			'create_redirects'     => false,
			'preserve_accents'     => false,
		];

		$this->settings_cache = self::sanitizeSettings(
			array_merge( $defaults, is_array( $saved ) ? $saved : [] )
		);
		return $this->settings_cache;
	}

	public static function sanitizeSettings( array $settings ): array {
		$allowed_case = [ 'lowercase', 'uppercase', 'titlecase', 'nochange' ];
		$case         = isset( $settings['case_transformation'] ) ? (string) $settings['case_transformation'] : 'lowercase';

		return [
			'default_pattern'      => isset( $settings['default_pattern'] ) ? sanitize_text_field( (string) $settings['default_pattern'] ) : '{title}',
			'case_transformation'  => in_array( $case, $allowed_case, true ) ? $case : 'lowercase',
			'max_filename_length'  => max( 10, min( 200, (int) ( $settings['max_filename_length'] ?? 50 ) ) ),
			'excluded_file_types'  => isset( $settings['excluded_file_types'] ) ? sanitize_text_field( (string) $settings['excluded_file_types'] ) : '',
			'add_unique_id_suffix' => self::settingEnabled( $settings, 'add_unique_id_suffix' ),
			'preserve_accents'     => self::settingEnabled( $settings, 'preserve_accents' ),
			'create_redirects'       => \WP_PowerSuite\Core\OptionStoreModuleSettingsRepository::isModuleEnabled( 'redirect-manager' )
				&& self::settingEnabled( $settings, 'create_redirects' ),
		];
	}

	public function renameUploadedFile( array $file ): array {
		if ( isset( $file['error'] ) && UPLOAD_ERR_OK !== (int) $file['error'] ) {
			return $file;
		}

		$original = basename( str_replace( "\0", '', (string) ( $file['name'] ?? '' ) ) );
		if ( $original === '' ) {
			return $file;
		}

		$ext = strtolower( pathinfo( $original, PATHINFO_EXTENSION ) );
		if ( $this->isExcludedExtension( $ext ) ) {
			return $file;
		}

		$base = pathinfo( $original, PATHINFO_FILENAME );
		$base = $this->slugify( (string) $base ) ?: 'file';
		$new_name = $this->formatFilename( $base, $ext );

		if ( $new_name !== '' && $new_name !== $original ) {
			$file['name'] = $new_name;
		}

		return $file;
	}

	public function addRenameMetaBox(): void {
		if ( ! current_user_can( 'upload_files' ) ) {
			return;
		}

		$post = get_post();
		if ( ! $post || $post->post_type !== 'attachment' || ! current_user_can( 'edit_post', $post->ID ) ) {
			return;
		}

		add_meta_box(
			'wpps-media-rename',
			__( 'Rename File', 'wppowersuite-lite' ),
			[ $this, 'renderRenameMetaBox' ],
			'attachment',
			'side'
		);
	}

	public function renderRenameMetaBox( \WP_Post $post ): void {
		$this->renderRenamePanel( $post );
	}

	public function addRenameFieldToAttachmentModal( array $form_fields, \WP_Post $post ): array {
		if ( ! $this->canRenameAttachment( $post->ID ) ) {
			return $form_fields;
		}

		ob_start();
		$this->renderRenamePanel( $post );
		$html = ob_get_clean();
		if ( ! is_string( $html ) || $html === '' ) {
			return $form_fields;
		}

		$form_fields['wpps_media_rename'] = [
			'label' => __( 'Rename File', 'wppowersuite-lite' ),
			'input' => 'html',
			'html'  => $html,
		];

		return $form_fields;
	}

	private function renderRenamePanel( \WP_Post $post ): void {
		$file = get_attached_file( $post->ID, true );
		if ( ! $file || ! is_file( $file ) ) {
			echo '<p>' . esc_html__( 'No file found.', 'wppowersuite-lite' ) . '</p>';
			return;
		}

		$current      = basename( $file );
		$default_name = $this->getDefaultRename( $post, $current );
		include __DIR__ . '/views/rename-meta-box.php';
	}

	private function getDefaultRename( \WP_Post $post, string $current_basename ): string {
		$base = $post->post_title ?? '';
		if ( trim( $base ) === '' ) {
			$base = pathinfo( $current_basename, PATHINFO_FILENAME );
		}
		return $this->slugify( $base ) ?: 'image';
	}

	public function addBulkRenameAction( array $actions ): array {
		$actions['wpps_rename_files'] = __( 'Rename Files', 'wppowersuite-lite' );
		return $actions;
	}

	public function handleBulkRenameAction( string $redirect, string $action, array $post_ids ): string {
		if ( $action !== 'wpps_rename_files' || empty( $post_ids ) ) {
			return $redirect;
		}

		$editable = array_values( array_filter( array_map( 'absint', $post_ids ), [ $this, 'canRenameAttachment' ] ) );
		if ( empty( $editable ) ) {
			return $redirect;
		}

		return add_query_arg(
			[
				'wpps_bulk_rename' => '1',
				'media'            => implode( ',', $editable ),
				'mode'             => 'list',
			],
			admin_url( 'upload.php' )
		);
	}

	public function bulkRenameNotice(): void {
		$ids = $this->getBulkRenameIdsFromRequest();
		if ( empty( $ids ) ) {
			return;
		}

		$count = count( $ids );
		?>
		<div class="notice notice-info wpps-bulk-rename-notice">
			<p>
				<?php
				printf(
					/* translators: 1: number, 2: number. */
					esc_html( _n( '%d file selected for renaming. Open the bulk rename modal below.', '%d files selected for renaming. Open the bulk rename modal below.', $count, 'wppowersuite-lite' ) ),
					$count
				);
				?>
			</p>
			<p>
				<button type="button" class="button button-primary"><?php esc_html_e( 'Open Bulk Rename Modal', 'wppowersuite-lite' ); ?></button>
			</p>
		</div>
		<?php
	}

	public function outputBulkRenameModal(): void {
		$ids = $this->getBulkRenameIdsFromRequest();
		if ( empty( $ids ) ) {
			return;
		}

		$settings = $this->getSettings();
		include __DIR__ . '/views/bulk-rename-modal.php';
	}

	public function enqueueAssets(): void {
		if ( ! current_user_can( 'upload_files' ) ) {
			return;
		}

		global $pagenow;

		if ( $pagenow === 'post.php' && ! empty( $_GET['post'] ) ) {
			$post_id = absint( $_GET['post'] );
			if ( get_post_type( $post_id ) === 'attachment' && current_user_can( 'edit_post', $post_id ) ) {
				$this->enqueueRenameAssets();
			}
			return;
		}

		if ( $pagenow === 'upload.php' ) {
			$this->enqueueRenameAssets();
		}
	}

	private function enqueueRenameAssets(): void {
		global $pagenow;

		$base = wp_powersuite_module_assets_url( __FILE__ );
		wp_enqueue_style( 'wp-powersuite-media-rename', $base . 'media-rename.css', [], self::MODULE_VERSION );
		wp_enqueue_script(
			'wp-powersuite-media-rename',
			$base . 'media-rename.js',
			$pagenow === 'upload.php' ? [ 'jquery', 'media' ] : [ 'jquery' ],
			self::MODULE_VERSION,
			true
		);
		wp_localize_script(
			'wp-powersuite-media-rename',
			'wppsMediaRename',
			[
				'ajaxUrl' => admin_url( 'admin-ajax.php' ),
				'nonce'   => wp_create_nonce( 'wpps_media_rename' ),
				'strings' => [
					'error'           => __( 'An error occurred.', 'wppowersuite-lite' ),
					'emptyFilename'   => __( 'Please enter a new filename.', 'wppowersuite-lite' ),
					'bulkComplete'    => __( 'Complete', 'wppowersuite-lite' ),
					'bulkSkipped'     => __( 'Skipped', 'wppowersuite-lite' ),
					'bulkFailed'      => __( 'Failed', 'wppowersuite-lite' ),
					'bulkSuccess'     => __( 'Success', 'wppowersuite-lite' ),
					'featuredIn'      => __( 'Featured image in', 'wppowersuite-lite' ),
					/* translators: %d: number value. */
					'postCount'       => __( '%d post', 'wppowersuite-lite' ),
					/* translators: %d: number value. */
					'postsCount'      => __( '%d posts', 'wppowersuite-lite' ),
					'postsInContent'  => __( 'Posts (in content)', 'wppowersuite-lite' ),
					'meta'            => __( 'Meta', 'wppowersuite-lite' ),
					'options'         => __( 'Options', 'wppowersuite-lite' ),
					'noReferences'    => __( 'No references found.', 'wppowersuite-lite' ),
					'dismiss'         => __( 'Dismiss', 'wppowersuite-lite' ),
					'noFilesSelected' => __( 'No files selected.', 'wppowersuite-lite' ),
				/* translators: %s: string value. */
				'renamedTo'       => __( 'Renamed to: %s', 'wppowersuite-lite' ),
				'renameLabel'     => __( 'Rename File', 'wppowersuite-lite' ),
				'loading'         => __( 'Loading…', 'wppowersuite-lite' ),
			],
			]
		);
	}

	public function ajaxRename(): void {
		$this->verifyAjaxNonce();

		$attachment_id = isset( $_POST['attachment_id'] ) ? absint( $_POST['attachment_id'] ) : 0;
		$new_name      = isset( $_POST['new_name'] ) ? sanitize_file_name( wp_unslash( (string) $_POST['new_name'] ) ) : '';
		$update_title  = ! empty( $_POST['update_title'] );

		if ( ! $this->canRenameAttachment( $attachment_id ) || $new_name === '' ) {
			wp_send_json_error( [ 'message' => __( 'Invalid request.', 'wppowersuite-lite' ) ] );
		}

		$result = $this->renameAttachment( $attachment_id, $new_name, $update_title );
		if ( is_wp_error( $result ) ) {
			wp_send_json_error( [ 'message' => $result->get_error_message() ] );
		}

		wp_send_json_success( $result );
	}

	public function ajaxScanReferences(): void {
		$this->verifyAjaxNonce();

		$attachment_id = isset( $_POST['attachment_id'] ) ? absint( $_POST['attachment_id'] ) : 0;
		if ( ! $this->canRenameAttachment( $attachment_id ) ) {
			wp_send_json_error( [ 'message' => __( 'Invalid request.', 'wppowersuite-lite' ) ] );
		}

		wp_send_json_success( $this->scanReferences( $attachment_id ) );
	}

	public function ajaxRenamePanel(): void {
		$this->verifyAjaxNonce();

		$attachment_id = isset( $_POST['attachment_id'] ) ? absint( $_POST['attachment_id'] ) : 0;
		if ( ! $this->canRenameAttachment( $attachment_id ) ) {
			wp_send_json_error( [ 'message' => __( 'Invalid request.', 'wppowersuite-lite' ) ] );
		}

		$post = get_post( $attachment_id );
		if ( ! $post instanceof \WP_Post ) {
			wp_send_json_error( [ 'message' => __( 'Invalid request.', 'wppowersuite-lite' ) ] );
		}

		ob_start();
		$this->renderRenamePanel( $post );
		$html = ob_get_clean();

		wp_send_json_success(
			[
				'html' => is_string( $html ) ? $html : '',
			]
		);
	}

	public function ajaxBulkRename(): void {
		$this->verifyAjaxNonce();

		$ids          = isset( $_POST['ids'] ) && is_array( $_POST['ids'] ) ? array_map( 'absint', $_POST['ids'] ) : [];
		$pattern      = isset( $_POST['pattern'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['pattern'] ) ) : '';
		$prefix       = isset( $_POST['prefix'] ) ? sanitize_file_name( wp_unslash( (string) $_POST['prefix'] ) ) : '';
		$suffix       = isset( $_POST['suffix'] ) ? sanitize_file_name( wp_unslash( (string) $_POST['suffix'] ) ) : '';
		$update_title = ! empty( $_POST['update_title'] );
		$counter      = isset( $_POST['counter'] ) ? max( 1, absint( $_POST['counter'] ) ) : 1;

		$ids = array_values( array_filter( $ids ) );
		if ( empty( $ids ) ) {
			wp_send_json_error( [ 'message' => __( 'No files selected.', 'wppowersuite-lite' ) ] );
		}

		$settings = $this->getSettings();
		$pattern  = $pattern !== '' ? $pattern : (string) ( $settings['default_pattern'] ?? '{title}' );
		$results  = [];

		foreach ( $ids as $id ) {
			if ( ! $this->canRenameAttachment( $id ) ) {
				$results[] = [
					'id'      => $id,
					'success' => false,
					'message' => __( 'Permission denied.', 'wppowersuite-lite' ),
				];
				continue;
			}

			$new_name = sanitize_file_name( $prefix . $this->resolvePattern( $pattern, $id, $counter ) . $suffix );
			$result   = $this->renameAttachment( $id, $new_name, $update_title );

			if ( is_wp_error( $result ) ) {
				$results[] = [
					'id'      => $id,
					'success' => false,
					'message' => $result->get_error_message(),
				];
			} else {
				$results[] = [
					'id'       => $id,
					'success'  => true,
					'new_name' => $result['new_name'] ?? '',
				];
			}

			++$counter;
		}

		wp_send_json_success(
			[
				'results' => $results,
				'counter' => $counter,
			]
		);
	}

	private function verifyAjaxNonce(): void {
		if ( ! check_ajax_referer( 'wpps_media_rename', 'nonce', false ) ) {
			wp_send_json_error( [ 'message' => __( 'Invalid security token.', 'wppowersuite-lite' ) ] );
		}

		if ( ! current_user_can( 'upload_files' ) ) {
			wp_send_json_error( [ 'message' => __( 'Permission denied.', 'wppowersuite-lite' ) ] );
		}
	}

	private function canRenameAttachment( int $attachment_id ): bool {
		return $attachment_id > 0
			&& get_post_type( $attachment_id ) === 'attachment'
			&& current_user_can( 'edit_post', $attachment_id );
	}

	private function getBulkRenameIdsFromRequest(): array {
		if ( ! current_user_can( 'upload_files' ) || ! isset( $_GET['media'] ) ) {
			return [];
		}

		$raw = sanitize_text_field( wp_unslash( (string) $_GET['media'] ) );
		$ids = array_values( array_filter( array_map( 'absint', explode( ',', $raw ) ) ) );

		return array_values( array_filter( $ids, [ $this, 'canRenameAttachment' ] ) );
	}

	public function renameAttachment( int $attachment_id, string $new_name, bool $update_title = false ): array|\WP_Error {
		$file = get_attached_file( $attachment_id, true );
		if ( ! $file || ! is_file( $file ) ) {
			return new \WP_Error( 'no_file', __( 'File not found.', 'wppowersuite-lite' ) );
		}

		$settings = $this->getSettings();
		$ext      = strtolower( pathinfo( $file, PATHINFO_EXTENSION ) );

		if ( $this->isExcludedExtension( $ext, $settings ) ) {
			return new \WP_Error( 'excluded', __( 'This file type is excluded from renaming', 'wppowersuite-lite' ) );
		}

		$path_info = pathinfo( $file );
		$dir       = $path_info['dirname'] ?? '';
		$old_ext   = $path_info['extension'] ?? '';

		$resolved = $this->sanitizeRenameBasename( $new_name, $old_ext, $settings );
		if ( is_wp_error( $resolved ) ) {
			return $resolved;
		}

		$new_name = $resolved;
		$new_file = $dir . DIRECTORY_SEPARATOR . $new_name;

		if ( $file === $new_file ) {
			return new \WP_Error( 'unchanged', __( 'Filename is unchanged.', 'wppowersuite-lite' ) );
		}
		if ( file_exists( $new_file ) ) {
			return new \WP_Error( 'exists', __( 'A file with that name already exists.', 'wppowersuite-lite' ) );
		}

		$old_url = wp_get_attachment_url( $attachment_id );
		if ( ! $old_url ) {
			return new \WP_Error( 'no_url', __( 'Could not get attachment URL.', 'wppowersuite-lite' ) );
		}

		$old_meta = wp_get_attachment_metadata( $attachment_id );
		if ( ! is_array( $old_meta ) ) {
			$old_meta = [];
		}

		if ( ! rename( $file, $new_file ) ) {
			return new \WP_Error( 'rename_failed', __( 'Failed to rename file on disk.', 'wppowersuite-lite' ) );
		}

		$upload_dir = wp_upload_dir();
		if ( ! empty( $upload_dir['error'] ) ) {
			rename( $new_file, $file );
			return new \WP_Error( 'upload_dir', __( 'Upload directory error.', 'wppowersuite-lite' ) );
		}

		$new_rel = ltrim( str_replace( $upload_dir['basedir'], '', $new_file ), DIRECTORY_SEPARATOR . '/' );
		$new_rel = str_replace( DIRECTORY_SEPARATOR, '/', $new_rel );

		update_post_meta( $attachment_id, '_wp_attached_file', $new_rel );

		$new_url = $upload_dir['baseurl'] . '/' . $new_rel;
		wp_update_post(
			[
				'ID'   => $attachment_id,
				'guid' => $new_url,
			]
		);

		if ( $update_title ) {
			$title_from_name = pathinfo( $new_name, PATHINFO_FILENAME );
			$title_from_name = str_replace( [ '-', '_' ], ' ', $title_from_name );
			wp_update_post(
				[
					'ID'         => $attachment_id,
					'post_title' => $title_from_name,
				]
			);
		}

		if ( is_array( $old_meta ) && ! empty( $old_meta['sizes'] ) ) {
			foreach ( $old_meta['sizes'] as $size_data ) {
				if ( empty( $size_data['file'] ) ) {
					continue;
				}
				$old_thumb = $dir . DIRECTORY_SEPARATOR . $size_data['file'];
				if ( is_file( $old_thumb ) ) {
					wp_delete_file( $old_thumb );
				}
			}
		}

		$meta = wp_generate_attachment_metadata( $attachment_id, $new_file );
		if ( is_wp_error( $meta ) || ! is_array( $meta ) ) {
			$meta = [];
		} else {
			wp_update_attachment_metadata( $attachment_id, $meta );
		}

		$this->updateReferences( $old_url, $new_url, basename( $file ), $new_name, $old_meta, $meta );

		if ( ! empty( $settings['create_redirects'] ) ) {
			$this->createRedirect( $old_url, $new_url );
		}

		return [
			'new_name' => $new_name,
			'new_url'  => $new_url,
			'old_url'  => $old_url,
		];
	}

	private function resolvePattern( string $pattern, int $attachment_id, int $counter ): string {
		$post = get_post( $attachment_id );
		if ( ! $post || $post->post_type !== 'attachment' ) {
			return sanitize_file_name( $pattern );
		}

		$title = $this->slugify( (string) ( $post->post_title ?? '' ) );

		$date = '';
		if ( $post->post_date ) {
			$date = gmdate( 'Y-m-d', strtotime( $post->post_date ) );
		}

		$post_title = '';
		$parent_id  = (int) $post->post_parent;
		if ( $parent_id > 0 ) {
			$parent = get_post( $parent_id );
			if ( $parent ) {
				$post_title = $this->slugify( (string) ( $parent->post_title ?? '' ) );
			}
		}

		$result = str_replace(
			[ '{title}', '{post-title}', '{id}', '{date}', '{counter}' ],
			[
				$title ?: 'image',
				$post_title ?: 'attachment',
				(string) $attachment_id,
				$date,
				sprintf( '%03d', $counter ),
			],
			$pattern
		);

		return sanitize_file_name( $result );
	}

	/**
	 * Whether an extension is unsafe to keep or produce during rename.
	 */
	public static function isRestrictedRenameExtension( string $ext ): bool {
		$ext = self::normalizeExtension( $ext );
		if ( $ext === '' ) {
			return false;
		}

		if ( in_array( $ext, self::RESTRICTED_RENAME_EXTENSIONS, true ) ) {
			return true;
		}

		return (bool) preg_match( '/^php\d+$/', $ext );
	}

	/**
	 * Build a same-folder basename. The original extension is always kept.
	 *
	 * @return string|\WP_Error
	 */
	public function sanitizeRenameBasename( string $requested_name, string $old_ext, ?array $settings = null ): string|\WP_Error {
		$settings = $settings ?? $this->getSettings();
		$old_ext  = self::normalizeExtension( $old_ext );

		if ( self::isRestrictedRenameExtension( $old_ext ) ) {
			return new \WP_Error( 'restricted_type', __( 'This file type cannot be renamed.', 'wppowersuite-lite' ) );
		}

		$requested_name = basename( str_replace( "\0", '', $requested_name ) );
		if ( $requested_name === '' || $requested_name === '.' || $requested_name === '..' ) {
			return new \WP_Error( 'invalid_name', __( 'Invalid filename.', 'wppowersuite-lite' ) );
		}

		$base = pathinfo( $requested_name, PATHINFO_FILENAME );
		$base = sanitize_file_name( $base );
		$base = self::stripRestrictedFilenameSegments( $base );
		if ( $base === '' || $base === '.' || $base === '..' ) {
			return new \WP_Error( 'invalid_name', __( 'Invalid filename.', 'wppowersuite-lite' ) );
		}

		$new_name  = $this->formatFilename( $base, $old_ext, $settings );
		$final_ext = self::normalizeExtension( (string) pathinfo( $new_name, PATHINFO_EXTENSION ) );

		if ( $final_ext !== $old_ext || self::isRestrictedRenameExtension( $final_ext ) ) {
			return new \WP_Error( 'extension_locked', __( 'The file extension cannot be changed.', 'wppowersuite-lite' ) );
		}

		if ( self::basenameContainsRestrictedExtension( $new_name ) ) {
			return new \WP_Error( 'invalid_name', __( 'Invalid filename.', 'wppowersuite-lite' ) );
		}

		return $new_name;
	}

	private static function normalizeExtension( string $ext ): string {
		return strtolower( trim( $ext, ". \t\n\r\0\x0B" ) );
	}

	private static function stripRestrictedFilenameSegments( string $base ): string {
		$kept = [];
		foreach ( explode( '.', $base ) as $part ) {
			if ( $part === '' || self::isRestrictedRenameExtension( $part ) ) {
				continue;
			}
			$kept[] = $part;
		}

		return implode( '.', $kept );
	}

	private static function basenameContainsRestrictedExtension( string $filename ): bool {
		$parts = explode( '.', strtolower( $filename ) );
		if ( $parts === [] ) {
			return false;
		}

		array_pop( $parts );
		foreach ( $parts as $part ) {
			if ( $part !== '' && self::isRestrictedRenameExtension( $part ) ) {
				return true;
			}
		}

		return false;
	}

	private function formatFilename( string $base, string $ext, ?array $settings = null ): string {
		$settings = $settings ?? $this->getSettings();
		$base     = $this->applyCaseTransformation( $base, (string) ( $settings['case_transformation'] ?? 'lowercase' ) );

		if ( ! self::settingEnabled( $settings, 'preserve_accents' ) ) {
			$base = $this->asciify( $base );
		}

		$max_len = (int) ( $settings['max_filename_length'] ?? 50 );
		if ( $max_len > 0 && strlen( $base ) > $max_len ) {
			$base = substr( $base, 0, $max_len );
		}

		if ( self::settingEnabled( $settings, 'add_unique_id_suffix' ) ) {
			$base .= '-' . $this->generateUniqueId();
		}

		$ext = strtolower( $ext );
		return $base . ( $ext !== '' ? '.' . $ext : '' );
	}

	private function isExcludedExtension( string $ext, ?array $settings = null ): bool {
		$ext = strtolower( $ext );
		if ( $ext === '' ) {
			return false;
		}

		$settings = $settings ?? $this->getSettings();
		$excluded = array_filter(
			array_map(
				'trim',
				array_map( 'strtolower', explode( ',', (string) ( $settings['excluded_file_types'] ?? '' ) ) )
			)
		);

		return $excluded !== [] && in_array( $ext, $excluded, true );
	}

	private static function settingEnabled( array $settings, string $key ): bool {
		if ( ! array_key_exists( $key, $settings ) ) {
			return false;
		}

		return filter_var( $settings[ $key ], FILTER_VALIDATE_BOOLEAN );
	}

	private function slugify( string $s ): string {
		$s = trim( $s );
		if ( $s === '' ) {
			return '';
		}

		$s = preg_replace( '/[^a-zA-Z0-9\s\-_]/', '', $s );
		$s = preg_replace( '/[\s\-_]+/', '-', $s );
		return trim( strtolower( (string) $s ), '-' );
	}

	private function applyCaseTransformation( string $s, string $case ): string {
		switch ( $case ) {
			case 'uppercase':
				return strtoupper( $s );
			case 'titlecase':
				return ucwords( strtolower( $s ), " \t\r\n\f\v-_" );
			case 'nochange':
				return $s;
			default:
				return strtolower( $s );
		}
	}

	private function asciify( string $s ): string {
		$s = preg_replace( '/[àáâãäå]/u', 'a', $s );
		$s = preg_replace( '/[èéêë]/u', 'e', $s );
		$s = preg_replace( '/[ìíîï]/u', 'i', $s );
		$s = preg_replace( '/[òóôõö]/u', 'o', $s );
		$s = preg_replace( '/[ùúûü]/u', 'u', $s );
		$s = preg_replace( '/[ñ]/u', 'n', $s );
		$s = preg_replace( '/[ýÿ]/u', 'y', $s );
		$s = preg_replace( '/[ç]/u', 'c', $s );
		return (string) $s;
	}

	private function generateUniqueId(): string {
		$chars = 'abcdefghjkmnpqrstuvwxyz23456789';
		$id    = '';
		for ( $i = 0; $i < 3; $i++ ) {
			$id .= $chars[ random_int( 0, strlen( $chars ) - 1 ) ];
		}
		return $id;
	}

	public function scanReferences( int $attachment_id ): array {
		$file = get_attached_file( $attachment_id, true );
		if ( ! $file || ! is_file( $file ) ) {
			return [ 'posts' => [], 'meta' => [], 'options' => [], 'featured_in' => [] ];
		}

		$filename = basename( $file );
		$url      = wp_get_attachment_url( $attachment_id );
		if ( ! $url ) {
			return [ 'posts' => [], 'meta' => [], 'options' => [], 'featured_in' => [] ];
		}

		global $wpdb;

		$posts = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT ID, post_title, post_type FROM {$wpdb->posts}
				WHERE post_type NOT IN ('revision', 'attachment')
				AND (post_content LIKE %s OR post_content LIKE %s)",
				'%' . $wpdb->esc_like( $filename ) . '%',
				'%' . $wpdb->esc_like( $url ) . '%'
			),
			ARRAY_A
		) ?: [];

		$featured_in = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT p.ID, p.post_title, p.post_type FROM {$wpdb->posts} p
				INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = '_thumbnail_id' AND pm.meta_value = %s
				WHERE p.post_type != 'revision' AND p.post_status != 'trash'",
				(string) $attachment_id
			),
			ARRAY_A
		) ?: [];

		$meta = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT meta_id, meta_key, post_id FROM {$wpdb->postmeta}
				WHERE meta_value LIKE %s
				AND post_id != %d
				AND meta_key NOT IN ('_wp_attached_file', '_wp_attachment_metadata')",
				'%' . $wpdb->esc_like( $filename ) . '%',
				$attachment_id
			),
			ARRAY_A
		) ?: [];

		$options = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT option_name FROM {$wpdb->options} WHERE option_value LIKE %s",
				'%' . $wpdb->esc_like( $filename ) . '%'
			)
		) ?: [];

		return [
			'posts'       => $posts,
			'meta'        => $meta,
			'options'     => $options,
			'featured_in' => $featured_in,
		];
	}

	/**
	 * @param array<string, mixed> $old_meta
	 * @param array<string, mixed> $new_meta
	 * @return array{urls: list<array{old:string,new:string}>, files: list<array{old:string,new:string}>}
	 */
	public static function collectReferencePairs(
		string $old_url,
		array $old_meta,
		string $new_url,
		array $new_meta,
		string $old_filename,
		string $new_filename
	): array {
		$urls  = [];
		$files = [];

		if ( $old_url !== '' && $new_url !== '' && $old_url !== $new_url ) {
			$urls[] = [
				'old' => $old_url,
				'new' => $new_url,
			];
		}

		if ( $old_filename !== '' && $new_filename !== '' && $old_filename !== $new_filename ) {
			$files[] = [
				'old' => $old_filename,
				'new' => $new_filename,
			];
		}

		$old_dir   = self::attachmentDirUrl( $old_url );
		$new_dir   = self::attachmentDirUrl( $new_url );
		$old_sizes = isset( $old_meta['sizes'] ) && is_array( $old_meta['sizes'] ) ? $old_meta['sizes'] : [];
		$new_sizes = isset( $new_meta['sizes'] ) && is_array( $new_meta['sizes'] ) ? $new_meta['sizes'] : [];

		foreach ( $old_sizes as $size => $old_data ) {
			if ( ! is_array( $old_data ) ) {
				continue;
			}

			$old_file = isset( $old_data['file'] ) ? basename( (string) $old_data['file'] ) : '';
			if ( $old_file === '' ) {
				continue;
			}

			$new_file = '';
			if ( isset( $new_sizes[ $size ] ) && is_array( $new_sizes[ $size ] ) && isset( $new_sizes[ $size ]['file'] ) ) {
				$new_file = basename( (string) $new_sizes[ $size ]['file'] );
			}

			$new_size_url = ( $new_file !== '' && $new_dir !== '' ) ? $new_dir . '/' . $new_file : $new_url;
			if ( $old_dir !== '' && $new_size_url !== '' ) {
				$urls[] = [
					'old' => $old_dir . '/' . $old_file,
					'new' => $new_size_url,
				];
			}

			if ( $new_file !== '' && $old_file !== $new_file ) {
				$files[] = [
					'old' => $old_file,
					'new' => $new_file,
				];
			}
		}

		return [
			'urls'  => $urls,
			'files' => $files,
		];
	}

	/**
	 * @param list<array{old:string,new:string}> $url_pairs
	 * @param list<array{old:string,new:string}> $filename_pairs
	 */
	public static function rewriteReferenceText( string $text, array $url_pairs, array $filename_pairs ): string {
		foreach ( self::sortPairsByOldLengthDesc( $url_pairs ) as $pair ) {
			if ( $pair['old'] === '' || $pair['old'] === $pair['new'] ) {
				continue;
			}
			$pattern = '/' . preg_quote( $pair['old'], '/' ) . '(?![A-Za-z0-9._-])/';
			$updated = preg_replace( $pattern, $pair['new'], $text );
			$text    = is_string( $updated ) ? $updated : $text;
		}

		foreach ( self::sortPairsByOldLengthDesc( $filename_pairs ) as $pair ) {
			if ( $pair['old'] === '' || $pair['old'] === $pair['new'] ) {
				continue;
			}
			$text = self::replaceBareFilenameToken( $text, $pair['old'], $pair['new'] );
		}

		return $text;
	}

	public static function replaceBareFilenameToken( string $text, string $old_filename, string $new_filename ): string {
		if ( $old_filename === '' || $old_filename === $new_filename ) {
			return $text;
		}

		$pattern = '#(?<![A-Za-z0-9._/-])' . preg_quote( $old_filename, '#' ) . '(?![A-Za-z0-9._-])#';
		$updated = preg_replace( $pattern, $new_filename, $text );

		return is_string( $updated ) ? $updated : $text;
	}

	/**
	 * @param array<string, mixed> $old_meta
	 * @param array<string, mixed> $new_meta
	 */
	private function updateReferences(
		string $old_url,
		string $new_url,
		string $old_filename,
		string $new_filename,
		array $old_meta,
		array $new_meta
	): void {
		$pairs = self::collectReferencePairs( $old_url, $old_meta, $new_url, $new_meta, $old_filename, $new_filename );
		$this->rewriteStoredReferences( $pairs['urls'], $pairs['files'] );
	}

	/**
	 * @param list<array{old:string,new:string}> $url_pairs
	 * @param list<array{old:string,new:string}> $filename_pairs
	 */
	private function rewriteStoredReferences( array $url_pairs, array $filename_pairs ): void {
		$needles = [];
		foreach ( array_merge( $url_pairs, $filename_pairs ) as $pair ) {
			if ( $pair['old'] !== '' && $pair['old'] !== $pair['new'] ) {
				$needles[ $pair['old'] ] = $pair['old'];
			}
		}

		if ( $needles === [] ) {
			return;
		}

		global $wpdb;

		$like_sql = [];
		$like_args = [];
		foreach ( $needles as $needle ) {
			$like_sql[]  = 'post_content LIKE %s';
			$like_args[] = '%' . $wpdb->esc_like( $needle ) . '%';
		}

		$posts = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT ID, post_content FROM {$wpdb->posts}
				WHERE post_type NOT IN ('revision')
				AND post_status NOT IN ('trash','auto-draft')
				AND (" . implode( ' OR ', $like_sql ) . ')',
				...$like_args
			),
			ARRAY_A
		) ?: [];

		foreach ( $posts as $row ) {
			$updated = self::rewriteReferenceText( (string) $row['post_content'], $url_pairs, $filename_pairs );
			if ( $updated === (string) $row['post_content'] ) {
				continue;
			}
			$wpdb->update(
				$wpdb->posts,
				[ 'post_content' => $updated ],
				[ 'ID' => (int) $row['ID'] ]
			);
		}

		$meta_like_sql  = [];
		$meta_like_args = [];
		foreach ( $needles as $needle ) {
			$meta_like_sql[]  = 'meta_value LIKE %s';
			$meta_like_args[] = '%' . $wpdb->esc_like( $needle ) . '%';
		}

		$meta_rows = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT meta_id, meta_value FROM {$wpdb->postmeta}
				WHERE meta_key NOT IN ('_wp_attached_file', '_wp_attachment_metadata')
				AND (" . implode( ' OR ', $meta_like_sql ) . ')',
				...$meta_like_args
			),
			ARRAY_A
		) ?: [];

		foreach ( $meta_rows as $row ) {
			$value = (string) $row['meta_value'];
			if ( is_serialized( $value ) ) {
				continue;
			}

			$updated = self::rewriteReferenceText( $value, $url_pairs, $filename_pairs );
			if ( $updated === $value ) {
				continue;
			}

			$wpdb->update(
				$wpdb->postmeta,
				[ 'meta_value' => $updated ],
				[ 'meta_id' => (int) $row['meta_id'] ]
			);
		}
	}

	private static function attachmentDirUrl( string $url ): string {
		if ( $url === '' || ! str_contains( $url, '/' ) ) {
			return '';
		}

		return (string) preg_replace( '#/[^/]+$#', '', $url );
	}

	/**
	 * @param list<array{old:string,new:string}> $pairs
	 * @return list<array{old:string,new:string}>
	 */
	private static function sortPairsByOldLengthDesc( array $pairs ): array {
		usort(
			$pairs,
			static function ( array $a, array $b ): int {
				return strlen( $b['old'] ) <=> strlen( $a['old'] );
			}
		);

		return $pairs;
	}

	private function createRedirect( string $old_url, string $new_url ): void {
		if ( ! class_exists( \RedirectManager\Module::class ) ) {
			return;
		}

		\RedirectManager\Module::addRedirectRule( $old_url, $new_url );
	}
}

