<?php
/**
 * Image Sizes Module
 *
 * Manage WordPress image sizes and disable unused ones.
 *
 * @package BlaminhorEssentials
 */

// Prevent direct access.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Image Sizes module class
 */
class Blaminhor_Essentials_Module_Image_Sizes extends Blaminhor_Essentials_Module {

    /**
     * Module ID
     *
     * @var string
     */
    protected $id = 'image-sizes';

    /**
     * Module name
     *
     * @var string
     */
    protected $name = 'Image Sizes';

    /**
     * Initialize module
     */
    protected function init() {
        $this->name        = __( 'Image Sizes', 'blaminhor-essentials' );
        $this->description = __( 'Manage WordPress image sizes and disable unused ones.', 'blaminhor-essentials' );
    }

    /**
     * Get default settings
     *
     * @return array
     */
    protected function get_default_settings() {
        return array(
            'disabled_sizes'        => array(),
            'custom_sizes'          => array(),
            'needs_regeneration'    => false,
            'sizes_pending_cleanup' => array(),
        );
    }

    /**
     * Register hooks
     */
    protected function register_hooks() {
        add_action( 'admin_init', array( $this, 'handle_settings_submission' ) );

        // Register custom sizes early.
        add_action( 'init', array( $this, 'register_custom_sizes' ), 1 );

        // Filter out disabled sizes.
        add_filter( 'intermediate_image_sizes_advanced', array( $this, 'filter_image_sizes' ), 10, 1 );
        add_filter( 'intermediate_image_sizes', array( $this, 'filter_image_sizes_simple' ), 10, 1 );

        // Add custom sizes to media editor dropdown.
        add_filter( 'image_size_names_choose', array( $this, 'add_custom_sizes_to_dropdown' ) );

        // AJAX handlers for thumbnail regeneration.
        add_action( 'wp_ajax_blaminhor_essentials_get_images', array( $this, 'ajax_get_images' ) );
        add_action( 'wp_ajax_blaminhor_essentials_regenerate_image', array( $this, 'ajax_regenerate_image' ) );
        add_action( 'wp_ajax_blaminhor_essentials_cleanup_disabled_thumbnails', array( $this, 'ajax_cleanup_disabled_thumbnails' ) );
        add_action( 'wp_ajax_blaminhor_essentials_dismiss_regen_notice', array( $this, 'ajax_dismiss_regen_notice' ) );
        add_action( 'wp_ajax_blaminhor_essentials_dismiss_cleanup_notice', array( $this, 'ajax_dismiss_cleanup_notice' ) );

        // Disable big image scaling if 'scaled' is disabled.
        $disabled = $this->get_setting( 'disabled_sizes', array() );
        if ( in_array( 'scaled', $disabled, true ) ) {
            add_filter( 'big_image_size_threshold', '__return_false' );
        }
    }

    /**
     * Register custom image sizes
     */
    public function register_custom_sizes() {
        $custom_sizes = $this->get_setting( 'custom_sizes', array() );

        if ( empty( $custom_sizes ) ) {
            return;
        }

        foreach ( $custom_sizes as $size ) {
            $name   = isset( $size['name'] ) ? sanitize_key( $size['name'] ) : '';
            $width  = isset( $size['width'] ) ? absint( $size['width'] ) : 0;
            $height = isset( $size['height'] ) ? absint( $size['height'] ) : 0;
            $crop   = ! empty( $size['crop'] );

            if ( ! empty( $name ) && ( $width > 0 || $height > 0 ) ) {
                add_image_size( $name, $width, $height, $crop );
            }
        }
    }

    /**
     * Filter out disabled image sizes (advanced)
     *
     * @param array $sizes Image sizes.
     * @return array
     */
    public function filter_image_sizes( $sizes ) {
        $disabled = $this->get_setting( 'disabled_sizes', array() );

        if ( empty( $disabled ) ) {
            return $sizes;
        }

        foreach ( $disabled as $size_name ) {
            if ( isset( $sizes[ $size_name ] ) ) {
                unset( $sizes[ $size_name ] );
            }
        }

        return $sizes;
    }

    /**
     * Filter out disabled image sizes (simple array)
     *
     * @param array $sizes Image size names.
     * @return array
     */
    public function filter_image_sizes_simple( $sizes ) {
        $disabled = $this->get_setting( 'disabled_sizes', array() );

        if ( empty( $disabled ) ) {
            return $sizes;
        }

        return array_diff( $sizes, $disabled );
    }

    /**
     * Add custom image sizes to the media editor dropdown
     *
     * @param array $sizes Existing image size names.
     * @return array
     */
    public function add_custom_sizes_to_dropdown( $sizes ) {
        $custom_sizes = $this->get_setting( 'custom_sizes', array() );
        $disabled     = $this->get_setting( 'disabled_sizes', array() );

        if ( empty( $custom_sizes ) ) {
            return $sizes;
        }

        foreach ( $custom_sizes as $size ) {
            $name = isset( $size['name'] ) ? sanitize_key( $size['name'] ) : '';

            // Skip if name is empty or size is disabled.
            if ( empty( $name ) || in_array( $name, $disabled, true ) ) {
                continue;
            }

            // Create a human-readable label from the size name.
            $label = ucwords( str_replace( array( '-', '_' ), ' ', $name ) );

            $sizes[ $name ] = $label;
        }

        return $sizes;
    }

    /**
     * AJAX: Get all image attachment IDs
     */
    public function ajax_get_images() {
        check_ajax_referer( 'blaminhor_essentials_image_sizes', 'nonce' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'blaminhor-essentials' ) ) );
        }

        global $wpdb;

        // Get all image attachment IDs.
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
        $image_ids = $wpdb->get_col(
            "SELECT ID FROM {$wpdb->posts}
             WHERE post_type = 'attachment'
             AND post_mime_type LIKE 'image/%'
             ORDER BY ID DESC"
        );

        wp_send_json_success( array(
            'total'  => count( $image_ids ),
            'images' => array_map( 'intval', $image_ids ),
        ) );
    }

    /**
     * AJAX: Regenerate thumbnails for a single image
     */
    public function ajax_regenerate_image() {
        check_ajax_referer( 'blaminhor_essentials_image_sizes', 'nonce' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'blaminhor-essentials' ) ) );
        }

        $attachment_id = isset( $_POST['attachment_id'] ) ? absint( $_POST['attachment_id'] ) : 0;
        $only_missing  = isset( $_POST['only_missing'] ) && sanitize_text_field( wp_unslash( $_POST['only_missing'] ) ) === 'true';

        if ( ! $attachment_id ) {
            wp_send_json_error( array( 'message' => __( 'Invalid attachment ID.', 'blaminhor-essentials' ) ) );
        }

        // Get the original file path.
        $file_path = get_attached_file( $attachment_id );

        if ( ! $file_path || ! file_exists( $file_path ) ) {
            wp_send_json_error( array(
                'message' => __( 'Original file not found.', 'blaminhor-essentials' ),
                'skipped' => true,
            ) );
        }

        // Get current metadata.
        $metadata = wp_get_attachment_metadata( $attachment_id );

        if ( ! $metadata ) {
            wp_send_json_error( array(
                'message' => __( 'Could not read image metadata.', 'blaminhor-essentials' ),
                'skipped' => true,
            ) );
        }

        // If only regenerating missing sizes, check what's missing.
        if ( $only_missing ) {
            $missing_sizes = $this->get_missing_sizes( $attachment_id, $metadata );
            if ( empty( $missing_sizes ) ) {
                wp_send_json_success( array(
                    'message' => __( 'All sizes already exist.', 'blaminhor-essentials' ),
                    'skipped' => true,
                ) );
            }
        }

        // Delete old thumbnail files (but not the original).
        $this->delete_attachment_thumbnails( $attachment_id, $metadata );

        // Regenerate metadata (this creates all the thumbnails).
        $new_metadata = wp_generate_attachment_metadata( $attachment_id, $file_path );

        if ( is_wp_error( $new_metadata ) || empty( $new_metadata ) ) {
            wp_send_json_error( array(
                'message' => __( 'Failed to regenerate thumbnails.', 'blaminhor-essentials' ),
            ) );
        }

        // Update the metadata.
        wp_update_attachment_metadata( $attachment_id, $new_metadata );

        // Get attachment title for display.
        $title = get_the_title( $attachment_id );
        if ( empty( $title ) ) {
            $title = basename( $file_path );
        }

        $sizes_count = isset( $new_metadata['sizes'] ) ? count( $new_metadata['sizes'] ) : 0;

        wp_send_json_success( array(
            'message' => sprintf(
                /* translators: 1: image title, 2: number of sizes */
                __( '%1$s: %2$d sizes generated', 'blaminhor-essentials' ),
                $title,
                $sizes_count
            ),
        ) );
    }

    /**
     * AJAX: Cleanup thumbnails for disabled sizes
     */
    public function ajax_cleanup_disabled_thumbnails() {
        check_ajax_referer( 'blaminhor_essentials_image_sizes', 'nonce' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'blaminhor-essentials' ) ) );
        }

        $attachment_id = isset( $_POST['attachment_id'] ) ? absint( $_POST['attachment_id'] ) : 0;
        $sizes_to_delete = isset( $_POST['sizes'] ) ? array_map( 'sanitize_key', wp_unslash( (array) $_POST['sizes'] ) ) : array();

        if ( ! $attachment_id ) {
            wp_send_json_error( array( 'message' => __( 'Invalid attachment ID.', 'blaminhor-essentials' ) ) );
        }

        if ( empty( $sizes_to_delete ) ) {
            wp_send_json_success( array(
                'message' => __( 'No sizes to delete.', 'blaminhor-essentials' ),
                'skipped' => true,
            ) );
        }

        $metadata = wp_get_attachment_metadata( $attachment_id );
        if ( ! $metadata || empty( $metadata['sizes'] ) ) {
            wp_send_json_success( array(
                'message' => __( 'No thumbnails found.', 'blaminhor-essentials' ),
                'skipped' => true,
            ) );
        }

        $file_path = get_attached_file( $attachment_id );
        $file_dir = dirname( $file_path );
        $deleted_count = 0;

        foreach ( $sizes_to_delete as $size_name ) {
            if ( isset( $metadata['sizes'][ $size_name ] ) ) {
                $thumb_file = $metadata['sizes'][ $size_name ]['file'];
                $thumb_path = $file_dir . '/' . $thumb_file;

                if ( file_exists( $thumb_path ) ) {
                    wp_delete_file( $thumb_path );
                    $deleted_count++;
                }

                unset( $metadata['sizes'][ $size_name ] );
            }
        }

        // Update metadata.
        wp_update_attachment_metadata( $attachment_id, $metadata );

        $title = get_the_title( $attachment_id );
        if ( empty( $title ) ) {
            $title = basename( $file_path );
        }

        wp_send_json_success( array(
            'message' => sprintf(
                /* translators: 1: image title, 2: number of deleted files */
                __( '%1$s: %2$d thumbnails deleted', 'blaminhor-essentials' ),
                $title,
                $deleted_count
            ),
            'deleted' => $deleted_count,
        ) );
    }

    /**
     * AJAX: Dismiss regeneration notice
     */
    public function ajax_dismiss_regen_notice() {
        check_ajax_referer( 'blaminhor_essentials_image_sizes', 'nonce' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'blaminhor-essentials' ) ) );
        }

        $settings = $this->settings;
        $settings['needs_regeneration'] = false;
        $this->save_settings( $settings );

        wp_send_json_success();
    }

    /**
     * AJAX: Dismiss cleanup notice
     */
    public function ajax_dismiss_cleanup_notice() {
        check_ajax_referer( 'blaminhor_essentials_image_sizes', 'nonce' );

        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied.', 'blaminhor-essentials' ) ) );
        }

        $settings = $this->settings;
        $settings['sizes_pending_cleanup'] = array();
        $this->save_settings( $settings );

        wp_send_json_success();
    }

    /**
     * Get missing image sizes for an attachment
     *
     * @param int   $attachment_id Attachment ID.
     * @param array $metadata      Current metadata.
     * @return array
     */
    private function get_missing_sizes( $attachment_id, $metadata ) {
        $all_sizes      = $this->get_all_image_sizes();
        $disabled_sizes = $this->get_setting( 'disabled_sizes', array() );
        $existing_sizes = isset( $metadata['sizes'] ) ? array_keys( $metadata['sizes'] ) : array();
        $missing        = array();

        foreach ( $all_sizes as $size_name => $size_data ) {
            // Skip disabled sizes.
            if ( in_array( $size_name, $disabled_sizes, true ) ) {
                continue;
            }

            // Skip 'scaled' as it's handled separately.
            if ( $size_name === 'scaled' ) {
                continue;
            }

            if ( ! in_array( $size_name, $existing_sizes, true ) ) {
                $missing[] = $size_name;
            }
        }

        return $missing;
    }

    /**
     * Delete thumbnail files for an attachment
     *
     * @param int   $attachment_id Attachment ID.
     * @param array $metadata      Attachment metadata.
     */
    private function delete_attachment_thumbnails( $attachment_id, $metadata ) {
        if ( empty( $metadata['sizes'] ) ) {
            return;
        }

        $upload_dir = wp_upload_dir();
        $file_path  = get_attached_file( $attachment_id );
        $file_dir   = dirname( $file_path );

        foreach ( $metadata['sizes'] as $size => $size_data ) {
            if ( isset( $size_data['file'] ) ) {
                $thumb_path = $file_dir . '/' . $size_data['file'];
                if ( file_exists( $thumb_path ) ) {
                    wp_delete_file( $thumb_path );
                }
            }
        }
    }

    /**
     * Get all registered image sizes with their details
     *
     * @return array
     */
    private function get_all_image_sizes() {
        global $_wp_additional_image_sizes;

        $sizes = array();

        // Get built-in sizes.
        $builtin = array( 'thumbnail', 'medium', 'medium_large', 'large' );

        foreach ( $builtin as $size ) {
            $sizes[ $size ] = array(
                'width'  => absint( get_option( $size . '_size_w' ) ),
                'height' => absint( get_option( $size . '_size_h' ) ),
                'crop'   => ( 'thumbnail' === $size ) ? (bool) get_option( 'thumbnail_crop' ) : false,
                'source' => 'core',
            );
        }

        // Add 'scaled' as a virtual size (WordPress 5.3+).
        $sizes['scaled'] = array(
            'width'  => 2560,
            'height' => 2560,
            'crop'   => false,
            'source' => 'core',
        );

        // Get additional sizes from themes and plugins.
        if ( ! empty( $_wp_additional_image_sizes ) && is_array( $_wp_additional_image_sizes ) ) {
            foreach ( $_wp_additional_image_sizes as $name => $size ) {
                $sizes[ $name ] = array(
                    'width'  => isset( $size['width'] ) ? absint( $size['width'] ) : 0,
                    'height' => isset( $size['height'] ) ? absint( $size['height'] ) : 0,
                    'crop'   => isset( $size['crop'] ) ? $size['crop'] : false,
                    'source' => $this->get_size_source( $name ),
                );
            }
        }

        // Ensure custom sizes are included even if not yet in global.
        $custom_sizes = $this->get_setting( 'custom_sizes', array() );
        foreach ( $custom_sizes as $custom ) {
            $name = isset( $custom['name'] ) ? sanitize_key( $custom['name'] ) : '';
            if ( ! empty( $name ) && ! isset( $sizes[ $name ] ) ) {
                $sizes[ $name ] = array(
                    'width'  => isset( $custom['width'] ) ? absint( $custom['width'] ) : 0,
                    'height' => isset( $custom['height'] ) ? absint( $custom['height'] ) : 0,
                    'crop'   => ! empty( $custom['crop'] ),
                    'source' => 'custom',
                );
            }
        }

        return $sizes;
    }

    /**
     * Get total number of images in library
     *
     * @return int
     */
    private function get_image_count() {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
        return (int) $wpdb->get_var(
            "SELECT COUNT(*) FROM {$wpdb->posts}
             WHERE post_type = 'attachment'
             AND post_mime_type LIKE 'image/%'"
        );
    }

    /**
     * Try to determine where a size was registered
     *
     * @param string $size_name Size name.
     * @return string
     */
    private function get_size_source( $size_name ) {
        $custom_sizes = $this->get_setting( 'custom_sizes', array() );

        foreach ( $custom_sizes as $size ) {
            if ( isset( $size['name'] ) && sanitize_key( $size['name'] ) === $size_name ) {
                return 'custom';
            }
        }

        // WooCommerce sizes.
        if ( strpos( $size_name, 'woocommerce' ) !== false || strpos( $size_name, 'shop' ) !== false ) {
            return 'plugin';
        }

        // Common theme patterns.
        $theme_patterns = array( 'theme', 'featured', 'hero', 'banner', 'slider', 'blog', 'post' );
        foreach ( $theme_patterns as $pattern ) {
            if ( strpos( $size_name, $pattern ) !== false ) {
                return 'theme';
            }
        }

        return 'theme';
    }

    /**
     * Get source label
     *
     * @param string $source Source identifier.
     * @return string
     */
    private function get_source_label( $source ) {
        $labels = array(
            'core'   => __( 'WordPress Core', 'blaminhor-essentials' ),
            'theme'  => __( 'Theme', 'blaminhor-essentials' ),
            'plugin' => __( 'Plugin', 'blaminhor-essentials' ),
            'custom' => __( 'Custom', 'blaminhor-essentials' ),
        );

        return isset( $labels[ $source ] ) ? $labels[ $source ] : $source;
    }

    /**
     * Check if size is a custom size
     *
     * @param string $size_name Size name.
     * @return bool
     */
    private function is_custom_size( $size_name ) {
        $custom_sizes = $this->get_setting( 'custom_sizes', array() );

        foreach ( $custom_sizes as $size ) {
            if ( isset( $size['name'] ) && sanitize_key( $size['name'] ) === $size_name ) {
                return true;
            }
        }

        return false;
    }

    /**
     * Get custom size index by name
     *
     * @param string $size_name Size name.
     * @return int|false
     */
    private function get_custom_size_index( $size_name ) {
        $custom_sizes = $this->get_setting( 'custom_sizes', array() );

        foreach ( $custom_sizes as $index => $size ) {
            if ( isset( $size['name'] ) && sanitize_key( $size['name'] ) === $size_name ) {
                return $index;
            }
        }

        return false;
    }

    /**
     * Process settings
     *
     * @param array $data Form data.
     */
    protected function process_settings( $data ) {
        $settings = $this->settings;

        // Handle toggling a size (enable/disable).
        if ( isset( $data['toggle_size'] ) ) {
            $size_name = sanitize_key( $data['toggle_size'] );
            $disabled  = isset( $settings['disabled_sizes'] ) ? $settings['disabled_sizes'] : array();

            if ( in_array( $size_name, $disabled, true ) ) {
                // Enable the size.
                $disabled = array_diff( $disabled, array( $size_name ) );
                // Mark that regeneration is needed.
                $settings['needs_regeneration'] = true;
                add_action( 'admin_notices', function () use ( $size_name ) {
                    /* translators: %s: size name */
                    blaminhor_essentials_admin_notice( sprintf( __( 'Size "%s" enabled.', 'blaminhor-essentials' ), $size_name ), 'success' );
                } );
            } else {
                // Disable the size.
                $disabled[] = $size_name;
                // Track for potential cleanup.
                $pending_cleanup = isset( $settings['sizes_pending_cleanup'] ) ? $settings['sizes_pending_cleanup'] : array();
                if ( ! in_array( $size_name, $pending_cleanup, true ) ) {
                    $pending_cleanup[] = $size_name;
                }
                $settings['sizes_pending_cleanup'] = $pending_cleanup;
                add_action( 'admin_notices', function () use ( $size_name ) {
                    /* translators: %s: size name */
                    blaminhor_essentials_admin_notice( sprintf( __( 'Size "%s" disabled.', 'blaminhor-essentials' ), $size_name ), 'success' );
                } );
            }

            $settings['disabled_sizes'] = array_values( array_unique( $disabled ) );
            $this->save_settings( $settings );
            return;
        }

        // Handle adding custom size.
        if ( isset( $data['add_custom_size'] ) ) {
            $name   = isset( $data['new_size_name'] ) ? sanitize_key( $data['new_size_name'] ) : '';
            $width  = isset( $data['new_size_width'] ) ? absint( $data['new_size_width'] ) : 0;
            $height = isset( $data['new_size_height'] ) ? absint( $data['new_size_height'] ) : 0;
            $crop   = isset( $data['new_size_crop'] );

            if ( empty( $name ) ) {
                add_action( 'admin_notices', function () {
                    blaminhor_essentials_admin_notice( __( 'Size name is required.', 'blaminhor-essentials' ), 'error' );
                } );
                return;
            }

            if ( $width <= 0 && $height <= 0 ) {
                add_action( 'admin_notices', function () {
                    blaminhor_essentials_admin_notice( __( 'At least one dimension (width or height) must be greater than 0.', 'blaminhor-essentials' ), 'error' );
                } );
                return;
            }

            // Check if name already exists.
            $all_sizes = $this->get_all_image_sizes();
            if ( isset( $all_sizes[ $name ] ) ) {
                add_action( 'admin_notices', function () {
                    blaminhor_essentials_admin_notice( __( 'Size name already exists.', 'blaminhor-essentials' ), 'error' );
                } );
                return;
            }

            if ( ! isset( $settings['custom_sizes'] ) || ! is_array( $settings['custom_sizes'] ) ) {
                $settings['custom_sizes'] = array();
            }

            $settings['custom_sizes'][] = array(
                'name'   => $name,
                'width'  => $width,
                'height' => $height,
                'crop'   => $crop,
            );

            // Mark that regeneration is needed.
            $settings['needs_regeneration'] = true;

            $this->save_settings( $settings );

            add_action( 'admin_notices', function () {
                blaminhor_essentials_admin_notice( __( 'Custom size added. New uploads will include this size.', 'blaminhor-essentials' ), 'success' );
            } );
            return;
        }

        // Handle removing custom size.
        if ( isset( $data['remove_custom_size'] ) ) {
            $size_name = sanitize_key( $data['remove_custom_size'] );
            $index     = $this->get_custom_size_index( $size_name );

            if ( false !== $index && isset( $settings['custom_sizes'][ $index ] ) ) {
                unset( $settings['custom_sizes'][ $index ] );
                $settings['custom_sizes'] = array_values( $settings['custom_sizes'] );

                // Also remove from disabled if it was disabled.
                if ( isset( $settings['disabled_sizes'] ) ) {
                    $settings['disabled_sizes'] = array_diff( $settings['disabled_sizes'], array( $size_name ) );
                    $settings['disabled_sizes'] = array_values( $settings['disabled_sizes'] );
                }

                $this->save_settings( $settings );

                add_action( 'admin_notices', function () {
                    blaminhor_essentials_admin_notice( __( 'Custom size removed.', 'blaminhor-essentials' ), 'success' );
                } );
            }
            return;
        }
    }

    /**
     * Render settings page
     */
    public function render_settings_page() {
        $all_sizes            = $this->get_all_image_sizes();
        $disabled_sizes       = $this->get_setting( 'disabled_sizes', array() );
        $custom_sizes         = $this->get_setting( 'custom_sizes', array() );
        $image_count          = $this->get_image_count();
        $needs_regeneration   = $this->get_setting( 'needs_regeneration', false );
        $sizes_pending_cleanup = $this->get_setting( 'sizes_pending_cleanup', array() );
        $nonce                = wp_create_nonce( 'blaminhor_essentials_image_sizes' );
        ?>
        <div class="wrap blaminhor-essentials-wrap">
            <div class="blaminhor-essentials-header">
                <h1><?php esc_html_e( 'Image Sizes', 'blaminhor-essentials' ); ?></h1>
                <p class="blaminhor-essentials-tagline"><?php esc_html_e( 'Manage WordPress image sizes and disable unused ones.', 'blaminhor-essentials' ); ?></p>
            </div>

            <?php if ( $needs_regeneration && $image_count > 0 ) : ?>
            <div class="ap-action-notice ap-notice-regen" id="ap-regen-notice">
                <div class="ap-notice-icon">
                    <span class="dashicons dashicons-update"></span>
                </div>
                <div class="ap-notice-content">
                    <strong><?php esc_html_e( 'Regeneration recommended', 'blaminhor-essentials' ); ?></strong>
                    <p><?php esc_html_e( 'You have added or enabled image sizes. Regenerate thumbnails to apply these changes to existing images.', 'blaminhor-essentials' ); ?></p>
                </div>
                <div class="ap-notice-actions">
                    <button type="button" class="button button-primary" onclick="jQuery('[data-tab=regenerate]').click();">
                        <?php esc_html_e( 'Regenerate Thumbnails', 'blaminhor-essentials' ); ?>
                    </button>
                    <button type="button" class="button ap-dismiss-notice" data-action="blaminhor_essentials_dismiss_regen_notice">
                        <?php esc_html_e( 'Dismiss', 'blaminhor-essentials' ); ?>
                    </button>
                </div>
            </div>
            <?php endif; ?>

            <?php if ( ! empty( $sizes_pending_cleanup ) && $image_count > 0 ) : ?>
            <div class="ap-action-notice ap-notice-cleanup" id="ap-cleanup-notice">
                <div class="ap-notice-icon">
                    <span class="dashicons dashicons-trash"></span>
                </div>
                <div class="ap-notice-content">
                    <strong><?php esc_html_e( 'Clean up unused thumbnails', 'blaminhor-essentials' ); ?></strong>
                    <p>
                        <?php
                        printf(
                            /* translators: %s: list of disabled sizes */
                            esc_html__( 'You disabled these sizes: %s. You can delete existing thumbnails for these sizes to free up disk space.', 'blaminhor-essentials' ),
                            '<code>' . esc_html( implode( '</code>, <code>', $sizes_pending_cleanup ) ) . '</code>'
                        );
                        ?>
                    </p>
                </div>
                <div class="ap-notice-actions">
                    <button type="button" class="button button-primary" onclick="jQuery('[data-tab=regenerate]').click(); setTimeout(function(){ document.querySelector('.ap-cleanup-options').scrollIntoView({behavior: 'smooth', block: 'start'}); }, 150);">
                        <?php esc_html_e( 'Clean Up', 'blaminhor-essentials' ); ?>
                    </button>
                    <button type="button" class="button ap-dismiss-notice" data-action="blaminhor_essentials_dismiss_cleanup_notice">
                        <?php esc_html_e( 'Dismiss', 'blaminhor-essentials' ); ?>
                    </button>
                </div>
            </div>
            <?php endif; ?>

            <div class="blaminhor-essentials-tabs-wrapper">
                <div class="blaminhor-essentials-tabs">
                    <button class="blaminhor-essentials-tab active" data-tab="sizes"><?php esc_html_e( 'All Sizes', 'blaminhor-essentials' ); ?></button>
                    <button class="blaminhor-essentials-tab" data-tab="custom"><?php esc_html_e( 'Add Custom Size', 'blaminhor-essentials' ); ?></button>
                    <button class="blaminhor-essentials-tab" data-tab="regenerate"><?php esc_html_e( 'Regenerate Thumbnails', 'blaminhor-essentials' ); ?></button>
                </div>

                <!-- All Sizes Tab -->
                <div class="blaminhor-essentials-tab-content active" data-tab-content="sizes">
                    <h3><?php esc_html_e( 'Registered Image Sizes', 'blaminhor-essentials' ); ?></h3>
                    <p class="description"><?php esc_html_e( 'Click the toggle button to enable or disable each size. Disabled sizes will not be generated when uploading new images.', 'blaminhor-essentials' ); ?></p>

                    <table class="widefat striped blaminhor-essentials-image-sizes-table ap-mt-md">
                        <thead>
                            <tr>
                                <th><?php esc_html_e( 'Size Name', 'blaminhor-essentials' ); ?></th>
                                <th><?php esc_html_e( 'Dimensions', 'blaminhor-essentials' ); ?></th>
                                <th><?php esc_html_e( 'Crop', 'blaminhor-essentials' ); ?></th>
                                <th><?php esc_html_e( 'Source', 'blaminhor-essentials' ); ?></th>
                                <th class="ap-col-actions-md"><?php esc_html_e( 'Status', 'blaminhor-essentials' ); ?></th>
                            </tr>
                        </thead>
                        <tbody>
                            <?php foreach ( $all_sizes as $name => $size ) : ?>
                                <?php
                                $is_disabled   = in_array( $name, $disabled_sizes, true );
                                $is_custom     = $this->is_custom_size( $name );
                                $row_class     = $is_disabled ? 'ap-size-disabled' : '';
                                $source_label  = $this->get_source_label( $size['source'] );
                                ?>
                                <tr class="<?php echo esc_attr( $row_class ); ?>">
                                    <td>
                                        <code class="<?php echo esc_attr( $is_disabled ? 'ap-code-disabled' : '' ); ?>"><?php echo esc_html( $name ); ?></code>
                                        <?php if ( $is_custom ) : ?>
                                            <span class="ap-badge ap-badge-custom"><?php esc_html_e( 'Custom', 'blaminhor-essentials' ); ?></span>
                                        <?php endif; ?>
                                    </td>
                                    <td>
                                        <?php
                                        $width  = $size['width'] ? $size['width'] . 'px' : __( 'Auto', 'blaminhor-essentials' );
                                        $height = $size['height'] ? $size['height'] . 'px' : __( 'Auto', 'blaminhor-essentials' );
                                        echo esc_html( $width . ' × ' . $height );
                                        ?>
                                    </td>
                                    <td>
                                        <?php
                                        if ( $size['crop'] ) {
                                            echo '<span class="ap-crop-yes">' . esc_html__( 'Yes', 'blaminhor-essentials' ) . '</span>';
                                        } else {
                                            echo '<span class="ap-crop-no">' . esc_html__( 'Proportional', 'blaminhor-essentials' ) . '</span>';
                                        }
                                        ?>
                                    </td>
                                    <td>
                                        <?php
                                        $source_class = 'ap-source-' . esc_attr( $size['source'] );
                                        ?>
                                        <span class="<?php echo esc_attr( $source_class ); ?>">
                                            <?php echo esc_html( $source_label ); ?>
                                        </span>
                                    </td>
                                    <td>
                                        <form method="post" class="ap-inline-form">
                                            <?php $this->render_nonce_field(); ?>
                                            <input type="hidden" name="toggle_size" value="<?php echo esc_attr( $name ); ?>">
                                            <?php if ( $is_disabled ) : ?>
                                                <button type="submit" class="button ap-btn-enable" title="<?php esc_attr_e( 'Click to enable', 'blaminhor-essentials' ); ?>">
                                                    <span class="dashicons dashicons-hidden"></span>
                                                    <?php esc_html_e( 'Disabled', 'blaminhor-essentials' ); ?>
                                                </button>
                                            <?php else : ?>
                                                <button type="submit" class="button ap-btn-disable" title="<?php esc_attr_e( 'Click to disable', 'blaminhor-essentials' ); ?>">
                                                    <span class="dashicons dashicons-visibility"></span>
                                                    <?php esc_html_e( 'Enabled', 'blaminhor-essentials' ); ?>
                                                </button>
                                            <?php endif; ?>
                                        </form>
                                        <?php if ( $is_custom ) : ?>
                                            <form method="post" class="ap-inline-form">
                                                <?php $this->render_nonce_field(); ?>
                                                <input type="hidden" name="remove_custom_size" value="<?php echo esc_attr( $name ); ?>">
                                                <button type="submit" class="button ap-btn-delete"
                                                        onclick="return confirm('<?php esc_attr_e( 'Delete this custom size?', 'blaminhor-essentials' ); ?>');"
                                                        title="<?php esc_attr_e( 'Delete', 'blaminhor-essentials' ); ?>">
                                                    <span class="dashicons dashicons-trash"></span>
                                                </button>
                                            </form>
                                        <?php endif; ?>
                                    </td>
                                </tr>
                            <?php endforeach; ?>
                        </tbody>
                    </table>

                    <div class="blaminhor-essentials-notice info ap-mt-md">
                        <strong><?php esc_html_e( 'Note', 'blaminhor-essentials' ); ?></strong><br>
                        <?php esc_html_e( 'Disabling a size prevents it from being generated for new uploads. Existing images are not affected.', 'blaminhor-essentials' ); ?>
                        <?php esc_html_e( 'Use the "Regenerate Thumbnails" tab to update existing images.', 'blaminhor-essentials' ); ?>
                    </div>
                </div>

                <!-- Add Custom Size Tab -->
                <div class="blaminhor-essentials-tab-content" data-tab-content="custom">
                    <h3><?php esc_html_e( 'Add Custom Size', 'blaminhor-essentials' ); ?></h3>
                    <p class="description"><?php esc_html_e( 'Create custom image sizes for your specific needs. These sizes will be available for new uploads.', 'blaminhor-essentials' ); ?></p>

                    <form method="post" class="ap-mt-md">
                        <?php $this->render_nonce_field(); ?>
                        <input type="hidden" name="add_custom_size" value="1">

                        <table class="form-table">
                            <tr>
                                <th scope="row">
                                    <label for="new_size_name"><?php esc_html_e( 'Size Name', 'blaminhor-essentials' ); ?> <span class="required">*</span></label>
                                </th>
                                <td>
                                    <input type="text" id="new_size_name" name="new_size_name" class="regular-text"
                                           placeholder="my-custom-size" pattern="[a-z0-9_-]+" required>
                                    <p class="description"><?php esc_html_e( 'Lowercase letters, numbers, hyphens, and underscores only.', 'blaminhor-essentials' ); ?></p>
                                </td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e( 'Dimensions', 'blaminhor-essentials' ); ?> <span class="required">*</span></th>
                                <td>
                                    <input type="number" name="new_size_width" id="new_size_width" class="small-text" min="0" max="9999" placeholder="800">
                                    <span> × </span>
                                    <input type="number" name="new_size_height" id="new_size_height" class="small-text" min="0" max="9999" placeholder="600">
                                    <span> <?php esc_html_e( 'px', 'blaminhor-essentials' ); ?></span>
                                    <p class="description"><?php esc_html_e( 'At least one dimension must be greater than 0. Set 0 for proportional scaling.', 'blaminhor-essentials' ); ?></p>
                                </td>
                            </tr>
                            <tr>
                                <th scope="row"><?php esc_html_e( 'Crop', 'blaminhor-essentials' ); ?></th>
                                <td>
                                    <label>
                                        <input type="checkbox" name="new_size_crop">
                                        <?php esc_html_e( 'Hard crop to exact dimensions', 'blaminhor-essentials' ); ?>
                                    </label>
                                    <p class="description"><?php esc_html_e( 'If unchecked, images will be resized proportionally to fit within the dimensions.', 'blaminhor-essentials' ); ?></p>
                                </td>
                            </tr>
                        </table>

                        <p class="submit">
                            <button type="submit" class="button button-primary"><?php esc_html_e( 'Add Size', 'blaminhor-essentials' ); ?></button>
                        </p>
                    </form>

                    <?php if ( ! empty( $custom_sizes ) ) : ?>
                        <hr>

                        <h3><?php esc_html_e( 'Your Custom Sizes', 'blaminhor-essentials' ); ?></h3>
                        <p class="description"><?php esc_html_e( 'These custom sizes are managed by Alfred Proteus. You can enable/disable them in the "All Sizes" tab.', 'blaminhor-essentials' ); ?></p>

                        <table class="widefat striped ap-mt-md">
                            <thead>
                                <tr>
                                    <th><?php esc_html_e( 'Size Name', 'blaminhor-essentials' ); ?></th>
                                    <th><?php esc_html_e( 'Dimensions', 'blaminhor-essentials' ); ?></th>
                                    <th><?php esc_html_e( 'Crop', 'blaminhor-essentials' ); ?></th>
                                    <th class="ap-col-actions-sm"></th>
                                </tr>
                            </thead>
                            <tbody>
                                <?php foreach ( $custom_sizes as $size ) : ?>
                                    <?php
                                    $size_name   = isset( $size['name'] ) ? sanitize_key( $size['name'] ) : '';
                                    $is_disabled = in_array( $size_name, $disabled_sizes, true );
                                    ?>
                                    <tr class="<?php echo esc_attr( $is_disabled ? 'ap-size-disabled' : '' ); ?>">
                                        <td>
                                            <code class="<?php echo esc_attr( $is_disabled ? 'ap-code-disabled' : '' ); ?>"><?php echo esc_html( $size_name ); ?></code>
                                            <?php if ( $is_disabled ) : ?>
                                                <span class="ap-badge ap-badge-disabled"><?php esc_html_e( 'Disabled', 'blaminhor-essentials' ); ?></span>
                                            <?php endif; ?>
                                        </td>
                                        <td>
                                            <?php
                                            $width  = ! empty( $size['width'] ) ? $size['width'] . 'px' : __( 'Auto', 'blaminhor-essentials' );
                                            $height = ! empty( $size['height'] ) ? $size['height'] . 'px' : __( 'Auto', 'blaminhor-essentials' );
                                            echo esc_html( $width . ' × ' . $height );
                                            ?>
                                        </td>
                                        <td>
                                            <?php echo ! empty( $size['crop'] ) ? esc_html__( 'Yes', 'blaminhor-essentials' ) : esc_html__( 'No', 'blaminhor-essentials' ); ?>
                                        </td>
                                        <td>
                                            <form method="post" class="ap-inline-form">
                                                <?php $this->render_nonce_field(); ?>
                                                <input type="hidden" name="remove_custom_size" value="<?php echo esc_attr( $size_name ); ?>">
                                                <button type="submit" class="button button-secondary button-small"
                                                        onclick="return confirm('<?php esc_attr_e( 'Delete this custom size?', 'blaminhor-essentials' ); ?>');">
                                                    <?php esc_html_e( 'Delete', 'blaminhor-essentials' ); ?>
                                                </button>
                                            </form>
                                        </td>
                                    </tr>
                                <?php endforeach; ?>
                            </tbody>
                        </table>
                    <?php endif; ?>
                </div>

                <!-- Regenerate Thumbnails Tab -->
                <div class="blaminhor-essentials-tab-content" data-tab-content="regenerate">
                    <h3><?php esc_html_e( 'Regenerate Thumbnails', 'blaminhor-essentials' ); ?></h3>
                    <p class="description">
                        <?php esc_html_e( 'Regenerate all image thumbnails to apply your current size settings to existing images.', 'blaminhor-essentials' ); ?>
                    </p>

                    <div class="ap-regen-stats ap-mt-lg">
                        <div class="ap-stat-box">
                            <span class="ap-stat-number"><?php echo esc_html( number_format_i18n( $image_count ) ); ?></span>
                            <span class="ap-stat-label"><?php esc_html_e( 'Images in library', 'blaminhor-essentials' ); ?></span>
                        </div>
                        <div class="ap-stat-box">
                            <span class="ap-stat-number"><?php echo esc_html( count( $all_sizes ) - count( $disabled_sizes ) ); ?></span>
                            <span class="ap-stat-label"><?php esc_html_e( 'Active sizes', 'blaminhor-essentials' ); ?></span>
                        </div>
                    </div>

                    <div class="ap-regen-options">
                        <h4><?php esc_html_e( 'Options', 'blaminhor-essentials' ); ?></h4>
                        <label class="ap-label-block">
                            <input type="checkbox" id="ap-regen-only-missing" checked>
                            <?php esc_html_e( 'Only regenerate missing sizes (faster)', 'blaminhor-essentials' ); ?>
                        </label>
                        <p class="description">
                            <?php esc_html_e( 'When checked, only generates thumbnails that do not exist yet. Uncheck to regenerate all thumbnails.', 'blaminhor-essentials' ); ?>
                        </p>
                    </div>

                    <div class="ap-regen-controls">
                        <button type="button" id="ap-start-regen" class="button button-primary button-hero">
                            <span class="dashicons dashicons-update ap-dashicon-align"></span>
                            <?php esc_html_e( 'Start Regeneration', 'blaminhor-essentials' ); ?>
                        </button>
                        <button type="button" id="ap-stop-regen" class="button button-secondary ap-hidden">
                            <?php esc_html_e( 'Stop', 'blaminhor-essentials' ); ?>
                        </button>
                    </div>

                    <!-- Progress Section -->
                    <div id="ap-regen-progress" class="ap-hidden ap-mt-lg">
                        <div class="ap-progress-header">
                            <span id="ap-progress-status"><?php esc_html_e( 'Preparing...', 'blaminhor-essentials' ); ?></span>
                            <span id="ap-progress-count">0 / 0</span>
                        </div>
                        <div class="ap-progress-bar-wrapper">
                            <div class="ap-progress-bar" id="ap-progress-bar" style="width: 0%;"></div>
                        </div>
                        <div id="ap-progress-log" class="ap-progress-log"></div>
                    </div>

                    <!-- Results Section -->
                    <div id="ap-regen-results" class="ap-hidden ap-mt-lg">
                        <div class="blaminhor-essentials-notice success">
                            <strong><?php esc_html_e( 'Regeneration Complete!', 'blaminhor-essentials' ); ?></strong><br>
                            <span id="ap-results-summary"></span>
                        </div>
                    </div>

                    <div class="blaminhor-essentials-notice warning ap-mt-lg">
                        <strong><?php esc_html_e( 'Warning', 'blaminhor-essentials' ); ?></strong><br>
                        <?php esc_html_e( 'This process may take a long time if you have many images. Do not close this page while regeneration is in progress.', 'blaminhor-essentials' ); ?>
                    </div>

                    <?php if ( ! empty( $disabled_sizes ) ) : ?>
                    <hr>

                    <h3><?php esc_html_e( 'Clean Up Disabled Sizes', 'blaminhor-essentials' ); ?></h3>
                    <p class="description">
                        <?php esc_html_e( 'Delete existing thumbnails for disabled sizes to free up disk space.', 'blaminhor-essentials' ); ?>
                    </p>

                    <div class="ap-cleanup-options">
                        <h4><?php esc_html_e( 'Select sizes to clean up', 'blaminhor-essentials' ); ?></h4>
                        <div class="ap-cleanup-sizes">
                            <?php foreach ( $disabled_sizes as $size_name ) : ?>
                                <label class="ap-label-block">
                                    <input type="checkbox" class="ap-cleanup-size" value="<?php echo esc_attr( $size_name ); ?>" checked>
                                    <code><?php echo esc_html( $size_name ); ?></code>
                                </label>
                            <?php endforeach; ?>
                        </div>
                    </div>

                    <div class="ap-regen-controls">
                        <button type="button" id="ap-start-cleanup" class="button button-secondary button-hero">
                            <span class="dashicons dashicons-trash ap-dashicon-align"></span>
                            <?php esc_html_e( 'Delete Selected Thumbnails', 'blaminhor-essentials' ); ?>
                        </button>
                        <button type="button" id="ap-stop-cleanup" class="button button-secondary ap-hidden">
                            <?php esc_html_e( 'Stop', 'blaminhor-essentials' ); ?>
                        </button>
                    </div>

                    <!-- Cleanup Progress Section -->
                    <div id="ap-cleanup-progress" class="ap-hidden ap-mt-lg">
                        <div class="ap-progress-header">
                            <span id="ap-cleanup-status"><?php esc_html_e( 'Preparing...', 'blaminhor-essentials' ); ?></span>
                            <span id="ap-cleanup-count">0 / 0</span>
                        </div>
                        <div class="ap-progress-bar-wrapper">
                            <div class="ap-progress-bar" id="ap-cleanup-bar" style="width: 0%;"></div>
                        </div>
                        <div id="ap-cleanup-log" class="ap-progress-log"></div>
                    </div>

                    <!-- Cleanup Results Section -->
                    <div id="ap-cleanup-results" class="ap-hidden ap-mt-lg">
                        <div class="blaminhor-essentials-notice success">
                            <strong><?php esc_html_e( 'Cleanup Complete!', 'blaminhor-essentials' ); ?></strong><br>
                            <span id="ap-cleanup-summary"></span>
                        </div>
                    </div>
                    <?php endif; ?>
                </div>
            </div>
        </div>
        <?php
    }

    /**
     * Enqueue module-specific assets
     *
     * @param string $hook Current admin page hook.
     */
    public function enqueue_assets( $hook ) {
        // Only load on this module's page.
        if ( strpos( $hook, 'blaminhor-essentials-image-sizes' ) === false ) {
            return;
        }

        $nonce = wp_create_nonce( 'blaminhor_essentials_image_sizes' );

        /* translators: 1: processed count, 2: skipped count, 3: errors count */
        $summary_text = __( 'Processed: %1$s | Skipped: %2$s | Errors: %3$s', 'blaminhor-essentials' );
        /* translators: 1: processed images count, 2: deleted thumbnails count, 3: errors count */
        $cleanup_summary = __( 'Images processed: %1$s | Thumbnails deleted: %2$s | Errors: %3$s', 'blaminhor-essentials' );

        $config = array(
            'nonce'   => $nonce,
            'ajaxurl' => admin_url( 'admin-ajax.php' ),
            'strings' => array(
                'loading'        => __( 'Loading images...', 'blaminhor-essentials' ),
                'processing'     => __( 'Processing...', 'blaminhor-essentials' ),
                'stopped'        => __( 'Stopped', 'blaminhor-essentials' ),
                'complete'       => __( 'Complete', 'blaminhor-essentials' ),
                'noImages'       => __( 'No images found in the library.', 'blaminhor-essentials' ),
                'errorLoading'   => __( 'Error loading images.', 'blaminhor-essentials' ),
                'ajaxError'      => __( 'AJAX error.', 'blaminhor-essentials' ),
                'requestFailed'  => __( 'Request failed', 'blaminhor-essentials' ),
                'selectSize'     => __( 'Please select at least one size to clean up.', 'blaminhor-essentials' ),
                'summary'        => $summary_text,
                'cleanupSummary' => $cleanup_summary,
            ),
        );

        wp_add_inline_script(
            'blaminhor-essentials-modules',
            'jQuery(document).ready(function($) { BlaminhorEssentialsImageSizes.init(' . wp_json_encode( $config ) . '); });'
        );
    }
}
