<?php
/**
 * Wrenio Fix Thumbnails Class
 *
 * Regenerates thumbnail metadata for R2-offloaded images. Normally WordPress
 * builds this from the local file, but if the file has been deleted locally
 * (delete_local_files enabled) we need to either:
 *  - read the uploaded R2 object to measure dimensions, or
 *  - use the globally registered image sizes as a reasonable default.
 *
 * This is separate from thumbnail generation: R2 already holds the resized
 * image files. We're only filling in the post meta so WordPress can render
 * them in the Media Library grid and srcset.
 *
 * @package Wrenio_Media_Offload
 * @since 1.0.0
 */

namespace Wrenio_Media_Offload;

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

/**
 * Class Wrenio_Fix_Thumbnails
 */
class Wrenio_Fix_Thumbnails {

    /** @var self|null */
    private static $instance = null;

    /** @var Wrenio_Logger */
    private $logger;

    /**
     * Get singleton instance
     */
    public static function get_instance() {
        if ( self::$instance === null ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Constructor — register the row action.
     */
    private function __construct() {
        $this->logger = Wrenio_Logger::get_instance();

        // Register the row action that enqueued JS will handle
        add_filter( 'media_row_actions', array( $this, 'add_fix_thumbnail_action' ), 10, 2 );
    }

    /**
     * Add "Fix Thumbnails" row action to the Media Library.
     *
     * v4 change: no inline onclick. The link has a data-attachment-id
     * attribute; assets/js/fix-thumbnails.js attaches a click handler.
     *
     * @param array    $actions
     * @param \WP_Post $post
     * @return array
     */
    public function add_fix_thumbnail_action( $actions, $post ) {
        if ( ! current_user_can( 'edit_post', $post->ID ) ) {
            return $actions;
        }
        if ( ! wp_attachment_is_image( $post->ID ) ) {
            return $actions;
        }

        $actions['wrenio_fix_thumbnail'] = sprintf(
            '<a href="#" class="wrenio-media-offload-fix-thumbnail" data-attachment-id="%d">%s</a>',
            (int) $post->ID,
            esc_html__( 'Fix Thumbnails', 'wrenio-media-offload' )
        );

        return $actions;
    }

    /**
     * AJAX handler for single-image thumbnail regeneration.
     *
     * Registered via add_action('wp_ajax_...') in the main plugin bootstrap
     * once this singleton is instantiated. We keep the registration here for
     * locality.
     */
    public static function register_single_ajax_handler() {
        add_action( 'wp_ajax_wrenio_media_offload_fix_single_thumbnail', array( self::get_instance(), 'ajax_fix_single' ) );
    }

    /**
     * AJAX handler for fixing a single attachment's thumbnails.
     */
    public function ajax_fix_single() {
        check_ajax_referer( 'wrenio_media_offload_fix_thumbnail', 'nonce' );

        $attachment_id = isset( $_POST['attachment_id'] ) ? (int) $_POST['attachment_id'] : 0;
        if ( $attachment_id <= 0 ) {
            wp_send_json_error( __( 'Invalid attachment ID', 'wrenio-media-offload' ) );
        }
        if ( ! current_user_can( 'edit_post', $attachment_id ) ) {
            wp_send_json_error( __( 'Permission denied', 'wrenio-media-offload' ) );
        }

        $result = $this->fix_single_thumbnail( $attachment_id );

        if ( $result ) {
            wp_send_json_success( array(
                'message' => __( 'Thumbnails regenerated successfully.', 'wrenio-media-offload' ),
            ) );
        }

        wp_send_json_error( __( 'Could not regenerate thumbnails for this attachment.', 'wrenio-media-offload' ) );
    }

    /**
     * Fix thumbnails for a single attachment.
     *
     * @param int $attachment_id
     * @return bool
     */
    public function fix_single_thumbnail( $attachment_id ) {
        $attachment_id = (int) $attachment_id;
        if ( $attachment_id <= 0 ) {
            return false;
        }

        if ( ! wp_attachment_is_image( $attachment_id ) ) {
            return false;
        }

        $metadata = wp_get_attachment_metadata( $attachment_id );
        $r2_url   = get_post_meta( $attachment_id, '_r2_url', true );

        // No R2 URL and no local file — nothing we can do
        $file_path = get_attached_file( $attachment_id );
        if ( empty( $r2_url ) && ( ! $file_path || ! file_exists( $file_path ) ) ) {
            return false;
        }

        // If local file exists, use the standard regeneration path
        if ( $file_path && file_exists( $file_path ) ) {
            require_once ABSPATH . 'wp-admin/includes/image.php';
            $new_metadata = wp_generate_attachment_metadata( $attachment_id, $file_path );
            if ( ! empty( $new_metadata ) ) {
                wp_update_attachment_metadata( $attachment_id, $new_metadata );
                $this->logger->info( "Fix Thumbnails: Regenerated metadata for {$attachment_id} from local file" );
                return true;
            }
        }

        // Local file missing — build plausible metadata from registered image sizes
        if ( empty( $metadata ) || ! is_array( $metadata ) ) {
            $metadata = array();
        }

        if ( empty( $metadata['sizes'] ) || ! is_array( $metadata['sizes'] ) ) {
            $metadata['sizes'] = array();
        }

        $filename    = $file_path ? basename( $file_path ) : basename( $r2_url );
        $dirname     = $file_path ? dirname( $filename ) : '';
        $name_parts  = pathinfo( $filename );
        $base_name   = $name_parts['filename'] ?? 'image';
        $extension   = $name_parts['extension'] ?? 'jpg';

        // Get base dimensions — prefer existing metadata, else read from R2 if safe
        $base_width  = $metadata['width']  ?? 0;
        $base_height = $metadata['height'] ?? 0;

        if ( ( $base_width === 0 || $base_height === 0 ) && ! empty( $r2_url ) ) {
            // Validate before fetching (avoid SSRF via stale/malicious meta)
            $parsed = wp_parse_url( $r2_url );
            if ( ! empty( $parsed['host'] ) && ! empty( $parsed['scheme'] ) && in_array( strtolower( $parsed['scheme'] ), array( 'http', 'https' ), true ) ) {
                // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
                $imagesize = @getimagesize( $r2_url );
                if ( $imagesize ) {
                    $base_width  = (int) $imagesize[0];
                    $base_height = (int) $imagesize[1];
                    $metadata['width']  = $base_width;
                    $metadata['height'] = $base_height;
                }
            }
        }

        if ( $base_width === 0 || $base_height === 0 ) {
            // Can't proceed without base dimensions
            $this->logger->warning( "Fix Thumbnails: Cannot determine base dimensions for {$attachment_id}" );
            return false;
        }

        // Build plausible thumbnail entries from registered sizes
        $registered_sizes = $this->get_registered_image_sizes();
        foreach ( $registered_sizes as $size_name => $size_info ) {
            if ( isset( $metadata['sizes'][ $size_name ] ) ) {
                continue; // Don't overwrite existing
            }

            $dims = $this->calculate_thumbnail_size(
                $base_width,
                $base_height,
                $size_info['width'],
                $size_info['height'],
                $size_info['crop'] ?? false
            );

            if ( $dims ) {
                $metadata['sizes'][ $size_name ] = array(
                    'file'      => sprintf( '%s-%dx%d.%s', $base_name, $dims['width'], $dims['height'], $extension ),
                    'width'     => $dims['width'],
                    'height'    => $dims['height'],
                    'mime-type' => get_post_mime_type( $attachment_id ) ?: 'image/jpeg',
                );
            }
        }

        if ( empty( $metadata['file'] ) ) {
            $attached_file    = get_post_meta( $attachment_id, '_wp_attached_file', true );
            $metadata['file'] = $attached_file ?: $filename;
        }

        wp_update_attachment_metadata( $attachment_id, $metadata );
        $this->logger->info( "Fix Thumbnails: Built metadata for {$attachment_id} from registered sizes" );

        return true;
    }

    /**
     * Get all registered image sizes with their dimensions and crop flag.
     *
     * @return array
     */
    private function get_registered_image_sizes() {
        global $_wp_additional_image_sizes;

        $sizes = array();
        foreach ( array( 'thumbnail', 'medium', 'medium_large', 'large' ) as $size ) {
            $sizes[ $size ] = array(
                'width'  => (int) get_option( "{$size}_size_w" ),
                'height' => (int) get_option( "{$size}_size_h" ),
                'crop'   => (bool) get_option( "{$size}_crop" ),
            );
        }

        if ( ! empty( $_wp_additional_image_sizes ) && is_array( $_wp_additional_image_sizes ) ) {
            foreach ( $_wp_additional_image_sizes as $name => $data ) {
                $sizes[ $name ] = array(
                    'width'  => (int) $data['width'],
                    'height' => (int) $data['height'],
                    'crop'   => (bool) ( $data['crop'] ?? false ),
                );
            }
        }

        return $sizes;
    }

    /**
     * Calculate sized thumbnail dimensions from base dimensions.
     *
     * @param int  $orig_w Original width
     * @param int  $orig_h Original height
     * @param int  $dest_w Desired width
     * @param int  $dest_h Desired height
     * @param bool $crop   Whether to crop or scale
     * @return array|null
     */
    private function calculate_thumbnail_size( $orig_w, $orig_h, $dest_w, $dest_h, $crop = false ) {
        if ( $dest_w <= 0 && $dest_h <= 0 ) {
            return null;
        }
        if ( $orig_w <= 0 || $orig_h <= 0 ) {
            return null;
        }

        if ( $crop && $dest_w > 0 && $dest_h > 0 ) {
            return array( 'width' => $dest_w, 'height' => $dest_h );
        }

        // Use WordPress's own constrain helper for consistency with core
        $dims = wp_constrain_dimensions( $orig_w, $orig_h, $dest_w ?: PHP_INT_MAX, $dest_h ?: PHP_INT_MAX );
        return array( 'width' => (int) $dims[0], 'height' => (int) $dims[1] );
    }
}

// Register the single-fix AJAX handler once the class is loaded
\Wrenio_Media_Offload\Wrenio_Fix_Thumbnails::register_single_ajax_handler();
