<?php
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Basic Image SEO — free version.
 *
 * Automatically adds missing alt text to images using a template
 * based on post title / filename / site name.
 *
 * @since 1.0.1
 */
class SEO_Fury_Image_SEO_Basic {

    /** @var string Option keys */
    private $opt_enabled  = 'seo_fury_image_seo_basic_enabled';
    private $opt_template = 'seo_fury_image_seo_alt_template';
    private $opt_title    = 'seo_fury_image_seo_add_title';

    public function __construct() {
        // Frontend hooks — only if enabled
        if ( ! is_admin() && get_option( $this->opt_enabled, 0 ) ) {
            add_filter( 'the_content', array( $this, 'add_missing_alt_text' ), 20 );
            add_filter( 'wp_get_attachment_image_attributes', array( $this, 'filter_attachment_image' ), 10, 3 );
        }

        // Bulk alt-text tool (admin AJAX)
        if ( is_admin() ) {
            add_action( 'wp_ajax_seo_fury_bulk_alt_scan', array( $this, 'ajax_bulk_alt_scan' ) );
            add_action( 'wp_ajax_seo_fury_bulk_alt_apply', array( $this, 'ajax_bulk_alt_apply' ) );
        }
    }

    /**
     * meta_query that matches image attachments with a missing OR empty alt.
     */
    private function missing_alt_meta_query() {
        return array(
            'relation' => 'OR',
            array( 'key' => '_wp_attachment_image_alt', 'compare' => 'NOT EXISTS' ),
            array( 'key' => '_wp_attachment_image_alt', 'value' => '', 'compare' => '=' ),
        );
    }

    /**
     * Build a human-readable alt text for one attachment from the template.
     */
    private function alt_for_attachment( $attachment_id, $template ) {
        $file     = get_attached_file( $attachment_id );
        $filename = $file ? pathinfo( $file, PATHINFO_FILENAME ) : '';
        $filename = preg_replace( '/-?\d+x\d+$/', '', $filename );
        $filename = ucfirst( trim( str_replace( array( '-', '_' ), ' ', $filename ) ) );

        $att          = get_post( $attachment_id );
        $parent_title = ( $att && $att->post_parent ) ? get_the_title( $att->post_parent ) : '';
        $title        = $parent_title !== '' ? $parent_title : ( $att ? $att->post_title : '' );

        return $this->generate_alt_text( $template, $title, $filename, 1 );
    }

    /**
     * AJAX: count image attachments missing alt text.
     */
    public function ajax_bulk_alt_scan() {
        check_ajax_referer( 'seo_fury_bulk_alt', 'nonce' );
        if ( ! current_user_can( 'upload_files' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied', 'seo-fury' ) ) );
        }

        $q = new WP_Query( array(
            'post_type'              => 'attachment',
            'post_status'            => 'inherit',
            'post_mime_type'         => 'image',
            'posts_per_page'         => 1,
            'fields'                 => 'ids',
            'no_found_rows'          => false,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
            'meta_query'             => $this->missing_alt_meta_query(),
        ) );

        wp_send_json_success( array( 'missing' => (int) $q->found_posts ) );
    }

    /**
     * AJAX: fill alt text for a batch of attachments that are missing it.
     * The JS calls this repeatedly until "remaining" reaches 0.
     */
    public function ajax_bulk_alt_apply() {
        check_ajax_referer( 'seo_fury_bulk_alt', 'nonce' );
        if ( ! current_user_can( 'upload_files' ) ) {
            wp_send_json_error( array( 'message' => __( 'Permission denied', 'seo-fury' ) ) );
        }

        $template = get_option( $this->opt_template, '{post_title} - {filename}' );

        $ids = get_posts( array(
            'post_type'              => 'attachment',
            'post_status'            => 'inherit',
            'post_mime_type'         => 'image',
            'fields'                 => 'ids',
            'posts_per_page'         => 25,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
            'meta_query'             => $this->missing_alt_meta_query(),
        ) );

        $done = 0;
        foreach ( $ids as $id ) {
            $alt = $this->alt_for_attachment( $id, $template );
            if ( $alt !== '' ) {
                update_post_meta( $id, '_wp_attachment_image_alt', $alt );
                $done++;
            } else {
                // Nothing to derive from — store a space-free fallback so the
                // attachment isn't re-scanned forever.
                update_post_meta( $id, '_wp_attachment_image_alt', get_the_title( $id ) );
                $done++;
            }
        }

        $remaining = new WP_Query( array(
            'post_type'      => 'attachment',
            'post_status'    => 'inherit',
            'post_mime_type' => 'image',
            'posts_per_page' => 1,
            'fields'         => 'ids',
            'no_found_rows'  => false,
            'meta_query'     => $this->missing_alt_meta_query(),
        ) );

        wp_send_json_success( array(
            'processed' => $done,
            'remaining' => (int) $remaining->found_posts,
        ) );
    }

    /**
     * Register settings for the admin page.
     */
    public function register_settings() {
        register_setting( 'my_seo_meta_group', $this->opt_enabled, 'absint' );
        register_setting( 'my_seo_meta_group', $this->opt_template, 'sanitize_text_field' );
        register_setting( 'my_seo_meta_group', $this->opt_title, 'absint' );
    }

    /**
     * Render settings UI for the SEO Images tab.
     */
    public function render_settings_page() {
        $enabled  = get_option( $this->opt_enabled, 0 );
        $template = get_option( $this->opt_template, '{post_title} - {filename}' );
        $add_title = get_option( $this->opt_title, 0 );
        ?>
        <div class="seo-card" style="max-width:700px; padding:20px;">
            <h3 style="margin-top:0;">
                <?php esc_html_e( 'Auto Alt Text', 'seo-fury' ); ?>
            </h3>
            <p style="color:var(--sf-gray-500,#64748B);margin-bottom:20px;">
                <?php esc_html_e( 'Automatically add alt text to images that are missing it. Uses a template based on the post title and image filename.', 'seo-fury' ); ?>
            </p>

            <table class="form-table" role="presentation">
                <tr>
                    <th scope="row"><?php esc_html_e( 'Enable', 'seo-fury' ); ?></th>
                    <td>
                        <label>
                            <input type="checkbox" name="<?php echo esc_attr( $this->opt_enabled ); ?>" value="1" <?php checked( $enabled, 1 ); ?>>
                            <?php esc_html_e( 'Add missing alt text to images automatically', 'seo-fury' ); ?>
                        </label>
                    </td>
                </tr>
                <tr>
                    <th scope="row"><?php esc_html_e( 'Alt Text Template', 'seo-fury' ); ?></th>
                    <td>
                        <input type="text" name="<?php echo esc_attr( $this->opt_template ); ?>"
                               value="<?php echo esc_attr( $template ); ?>"
                               class="regular-text" style="width:100%;max-width:400px;">
                        <p class="description">
                            <?php esc_html_e( 'Available variables:', 'seo-fury' ); ?>
                            <code>{post_title}</code>, <code>{filename}</code>, <code>{site_name}</code>, <code>{image_index}</code>
                        </p>
                    </td>
                </tr>
                <tr>
                    <th scope="row"><?php esc_html_e( 'Title Attribute', 'seo-fury' ); ?></th>
                    <td>
                        <label>
                            <input type="checkbox" name="<?php echo esc_attr( $this->opt_title ); ?>" value="1" <?php checked( $add_title, 1 ); ?>>
                            <?php esc_html_e( 'Also add title attribute to images', 'seo-fury' ); ?>
                        </label>
                    </td>
                </tr>
            </table>

            <hr style="margin:24px 0;border:none;border-top:1px solid var(--sf-gray-200,#e5e7eb);">

            <h3 style="margin-top:0;"><?php esc_html_e( 'Bulk Alt Text', 'seo-fury' ); ?></h3>
            <p style="color:var(--sf-gray-500,#64748B);margin-bottom:16px;">
                <?php esc_html_e( 'Write real alt text to your Media Library for every image that is missing it, using the template above. Unlike the automatic option, this saves the alt text permanently to each attachment.', 'seo-fury' ); ?>
            </p>

            <div id="sf-bulk-alt" data-nonce="<?php echo esc_attr( wp_create_nonce( 'seo_fury_bulk_alt' ) ); ?>">
                <p>
                    <button type="button" class="button" id="sf-bulk-alt-scan"><?php esc_html_e( 'Scan images', 'seo-fury' ); ?></button>
                    <button type="button" class="button button-primary" id="sf-bulk-alt-apply" style="display:none;"><?php esc_html_e( 'Generate &amp; apply alt text', 'seo-fury' ); ?></button>
                </p>
                <p id="sf-bulk-alt-status" style="margin:8px 0;"></p>
                <div id="sf-bulk-alt-progress" style="display:none;height:8px;border-radius:999px;background:var(--sf-gray-200,#e5e7eb);overflow:hidden;max-width:400px;">
                    <div id="sf-bulk-alt-bar" style="height:100%;width:0;background:#2563eb;transition:width .2s;"></div>
                </div>
            </div>

            <script>
            (function($){
                var $wrap = $('#sf-bulk-alt'); if (!$wrap.length) { return; }
                var nonce = $wrap.data('nonce');
                var i18n = {
                    scanning: <?php echo wp_json_encode( __( 'Scanning…', 'seo-fury' ) ); ?>,
                    none: <?php echo wp_json_encode( __( 'All images already have alt text. ✅', 'seo-fury' ) ); ?>,
                    found: <?php echo wp_json_encode( __( '%d images are missing alt text.', 'seo-fury' ) ); ?>,
                    working: <?php echo wp_json_encode( __( 'Applying… %d remaining', 'seo-fury' ) ); ?>,
                    done: <?php echo wp_json_encode( __( 'Done — alt text applied to %d images. ✅', 'seo-fury' ) ); ?>,
                    error: <?php echo wp_json_encode( __( 'Something went wrong. Please try again.', 'seo-fury' ) ); ?>
                };
                var $scan = $('#sf-bulk-alt-scan'), $apply = $('#sf-bulk-alt-apply'),
                    $status = $('#sf-bulk-alt-status'), $progress = $('#sf-bulk-alt-progress'), $bar = $('#sf-bulk-alt-bar');
                var total = 0, appliedTotal = 0;

                function post(action, cb){
                    $.post(ajaxurl, { action: action, nonce: nonce }, function(r){ cb(r); }).fail(function(){
                        $status.text(i18n.error); $scan.prop('disabled', false);
                    });
                }

                $scan.on('click', function(){
                    $scan.prop('disabled', true); $apply.hide(); $progress.hide();
                    $status.text(i18n.scanning);
                    post('seo_fury_bulk_alt_scan', function(r){
                        $scan.prop('disabled', false);
                        if (!r || !r.success) { $status.text(i18n.error); return; }
                        total = parseInt(r.data.missing, 10) || 0;
                        if (total === 0) { $status.text(i18n.none); $apply.hide(); }
                        else { $status.text(i18n.found.replace('%d', total)); $apply.show(); }
                    });
                });

                $apply.on('click', function(){
                    $apply.prop('disabled', true); $scan.prop('disabled', true);
                    appliedTotal = 0; $progress.show(); $bar.css('width', '0');
                    step();
                });

                function step(){
                    post('seo_fury_bulk_alt_apply', function(r){
                        if (!r || !r.success) { $status.text(i18n.error); $apply.prop('disabled', false); $scan.prop('disabled', false); return; }
                        appliedTotal += parseInt(r.data.processed, 10) || 0;
                        var remaining = parseInt(r.data.remaining, 10) || 0;
                        if (total > 0) { $bar.css('width', Math.round(((total - remaining) / total) * 100) + '%'); }
                        if (remaining > 0 && r.data.processed > 0) {
                            $status.text(i18n.working.replace('%d', remaining));
                            step();
                        } else {
                            $bar.css('width', '100%');
                            $status.text(i18n.done.replace('%d', appliedTotal));
                            $apply.prop('disabled', false).hide();
                            $scan.prop('disabled', false);
                        }
                    });
                }
            })(jQuery);
            </script>
        </div>
        <?php
    }

    /**
     * Filter post content to add missing alt text to images.
     *
     * @param string $content Post content.
     * @return string Modified content.
     */
    public function add_missing_alt_text( $content ) {
        if ( empty( $content ) || strpos( $content, '<img' ) === false ) {
            return $content;
        }

        $post = get_post();
        if ( ! $post ) {
            return $content;
        }

        $template  = get_option( $this->opt_template, '{post_title} - {filename}' );
        $add_title = get_option( $this->opt_title, 0 );
        $index     = 0;

        $content = preg_replace_callback(
            '/<img\b([^>]*)>/i',
            function ( $matches ) use ( $post, $template, $add_title, &$index ) {
                $tag   = $matches[0];
                $attrs = $matches[1];
                $index++;

                // Check if alt already exists and is non-empty — accept single,
                // double, or unquoted values. Matching only double quotes meant
                // alt='...' images were treated as missing and got a SECOND alt
                // attribute (invalid HTML).
                if ( preg_match( '/\balt\s*=\s*(?:"([^"]*)"|\'([^\']*)\'|([^\s"\'>]+))/i', $attrs, $am ) ) {
                    $existing = '';
                    if ( isset( $am[1] ) && $am[1] !== '' ) {
                        $existing = $am[1];
                    } elseif ( isset( $am[2] ) && $am[2] !== '' ) {
                        $existing = $am[2];
                    } elseif ( isset( $am[3] ) ) {
                        $existing = $am[3];
                    }
                    if ( trim( $existing ) !== '' ) {
                        return $tag; // already has a non-empty alt
                    }
                }

                // Extract filename from src
                $filename = '';
                if ( preg_match( '/\bsrc\s*=\s*"([^"]+)"/i', $attrs, $src_match ) ) {
                    $filename = pathinfo( wp_parse_url( $src_match[1], PHP_URL_PATH ), PATHINFO_FILENAME );
                    $filename = str_replace( array( '-', '_' ), ' ', $filename );
                    // Remove dimension suffixes like "image-300x200"
                    $filename = preg_replace( '/-?\d+x\d+$/', '', $filename );
                    $filename = trim( $filename );
                }

                $alt_text = $this->generate_alt_text( $template, $post->post_title, $filename, $index );

                // Remove existing empty alt="" or alt='' before adding ours
                $attrs = preg_replace( '/\balt\s*=\s*(?:""|\'\')\s*/i', '', $attrs );

                $new_tag = '<img ' . trim( $attrs ) . ' alt="' . esc_attr( $alt_text ) . '"';

                // Add title attribute if enabled and not present
                if ( $add_title && ! preg_match( '/\btitle\s*=/i', $attrs ) ) {
                    $new_tag .= ' title="' . esc_attr( $alt_text ) . '"';
                }

                $new_tag .= '>';
                return $new_tag;
            },
            $content
        );

        return $content;
    }

    /**
     * Filter attachment image attributes.
     *
     * @param array   $attr       Attributes.
     * @param WP_Post $attachment Attachment post.
     * @param mixed   $size       Image size.
     * @return array Modified attributes.
     */
    public function filter_attachment_image( $attr, $attachment, $size ) {
        if ( ! empty( $attr['alt'] ) ) {
            return $attr;
        }

        $template  = get_option( $this->opt_template, '{post_title} - {filename}' );
        $post      = get_post();
        $post_title = $post ? $post->post_title : get_the_title( $attachment->ID );
        $filename   = pathinfo( get_attached_file( $attachment->ID ), PATHINFO_FILENAME );
        $filename   = str_replace( array( '-', '_' ), ' ', $filename );

        $attr['alt'] = $this->generate_alt_text( $template, $post_title, $filename, 1 );

        if ( get_option( $this->opt_title, 0 ) && empty( $attr['title'] ) ) {
            $attr['title'] = $attr['alt'];
        }

        return $attr;
    }

    /**
     * Generate alt text from template.
     *
     * @param string $template   Template with placeholders.
     * @param string $post_title Post title.
     * @param string $filename   Image filename (cleaned).
     * @param int    $index      Image index in content.
     * @return string Generated alt text.
     */
    private function generate_alt_text( $template, $post_title, $filename, $index ) {
        $replacements = array(
            '{post_title}'   => $post_title,
            '{filename}'     => $filename ?: $post_title,
            '{site_name}'    => get_bloginfo( 'name' ),
            '{image_index}'  => $index,
        );

        return trim( str_replace(
            array_keys( $replacements ),
            array_values( $replacements ),
            $template
        ) );
    }
}
