<?php
namespace ZS_SEO\AuditOptimization\Analyzers;

defined( 'ABSPATH' ) || exit;

class SeoAnalyzer {

    private int $post_id;
    private \WP_Post $post;
    private string $content;        // raw post_content (for meta checks)
    private string $rendered_html;  // actual frontend HTML (for heading/image/word checks)

    public function __construct( int $post_id ) {
        $this->post_id      = $post_id;
        $this->post         = get_post( $post_id );
        $this->content      = $this->post->post_content ?? '';
        $this->rendered_html = $this->fetch_rendered_html( $post_id );
    }

    /**
     * Fetch actual rendered frontend HTML of the page.
     * This works with ANY theme — custom, Elementor, Divi, Gutenberg, etc.
     */
    private function fetch_rendered_html( int $post_id ): string {
        $url = get_permalink( $post_id );
        if ( ! $url ) return $this->content;

        $response = wp_remote_get( $url, [
            'timeout'    => 15,
            'user-agent' => 'ZS_SEO-Analyzer/1.0 (WordPress SEO Audit)',
            'sslverify'  => false,
        ] );

        if ( is_wp_error( $response ) ) return $this->content;

        $code = wp_remote_retrieve_response_code( $response );
        if ( $code !== 200 ) return $this->content;

        $html = wp_remote_retrieve_body( $response );
        if ( ! $html ) return $this->content;

        // Strip HTML comments to avoid false positives from commented-out code
        $html = preg_replace( '/<!--.*?-->/s', '', $html );

        return $html;
    }

    // ── Find image filename in active theme template files ─────────────────────
    private function find_image_in_theme( string $src ): string {
        if ( empty( $src ) ) return '';

        $filename  = basename( strtok( $src, '?' ) );
        if ( empty( $filename ) ) return '';

        $theme_dir = get_template_directory();
        $theme_uri = get_template_directory_uri();

        // Walk all .php files in the active theme directory
        $iterator = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator( $theme_dir, \RecursiveDirectoryIterator::SKIP_DOTS )
        );

        foreach ( $iterator as $file ) {
            if ( $file->getExtension() !== 'php' ) continue;

            $file_content = @file_get_contents( $file->getPathname() );
            if ( $file_content === false ) continue;

            // Check for filename reference in file
            if ( strpos( $file_content, $filename ) !== false ) {
                // Find the line number
                $lines    = explode( "\n", $file_content );
                $line_num = 0;
                foreach ( $lines as $i => $line ) {
                    if ( strpos( $line, $filename ) !== false ) {
                        $line_num = $i + 1;
                        break;
                    }
                }
                // Return path relative to wp-content
                $rel = str_replace( WP_CONTENT_DIR, basename( WP_CONTENT_DIR ), $file->getPathname() );
                $rel = str_replace( '\\', '/', $rel );
                return $rel . ( $line_num ? ':' . $line_num : '' );
            }
        }

        return '';
    }

    // ── Run full audit ─────────────────────────────────────────────────────────
    public function run(): array {
        return [
            'score'         => 0,
            'on_page'       => $this->analyze_on_page(),
            'headings'      => $this->analyze_headings(),
            'images'        => $this->analyze_images(),
            'performance'   => $this->analyze_performance(),
            'accessibility' => $this->analyze_accessibility(),
            'geo'           => $this->analyze_geo(),
        ];
    }

    // ── On-Page SEO ────────────────────────────────────────────────────────────
    private function analyze_on_page(): array {
        $issues = [];
        $good   = [];

        $meta_title = get_post_meta( $this->post_id, '_zs_seo_title', true );
        $meta_desc  = get_post_meta( $this->post_id, '_zs_seo_description', true );
        $canonical  = get_post_meta( $this->post_id, '_zs_seo_canonical', true );

        // Title checks
        if ( empty( $meta_title ) ) {
            $issues[] = [ 'type' => 'warning', 'msg' => __('Custom SEO title not set. Using post title.', 'zs-seo-audit-optimization') ];
        } else {
            $len = mb_strlen( $meta_title );
            if ( $len < 30 ) {
                /* translators: %d: character count */
                $issues[] = [ 'type' => 'warning', 'msg' => sprintf( __('SEO title too short (%d chars). Aim for 50–60.', 'zs-seo-audit-optimization'), $len ) ];
            } elseif ( $len > 60 ) {
                /* translators: %d: character count */
                $issues[] = [ 'type' => 'warning', 'msg' => sprintf( __('SEO title too long (%d chars). Keep under 60.', 'zs-seo-audit-optimization'), $len ) ];
            } else {
                $good[] = __('SEO title length is optimal.', 'zs-seo-audit-optimization');
            }
        }

        // Description checks
        if ( empty( $meta_desc ) ) {
            $issues[] = [ 'type' => 'error', 'msg' => __('Meta description is missing.', 'zs-seo-audit-optimization') ];
        } else {
            $len = mb_strlen( $meta_desc );
            if ( $len < 120 ) {
                /* translators: %d: character count */
                $issues[] = [ 'type' => 'warning', 'msg' => sprintf( __('Meta description too short (%d chars). Aim for 150–160.', 'zs-seo-audit-optimization'), $len ) ];
            } elseif ( $len > 160 ) {
                /* translators: %d: character count */
                $issues[] = [ 'type' => 'warning', 'msg' => sprintf( __('Meta description too long (%d chars). Keep under 160.', 'zs-seo-audit-optimization'), $len ) ];
            } else {
                $good[] = __('Meta description length is optimal.', 'zs-seo-audit-optimization');
            }
        }

        // Canonical
        if ( empty( $canonical ) ) {
            $issues[] = [ 'type' => 'info', 'msg' => __('No custom canonical set. Auto-canonical will be used.', 'zs-seo-audit-optimization') ];
        } else {
            $good[] = __('Custom canonical URL is set.', 'zs-seo-audit-optimization');
        }

        // Word count from RENDERED html — extracts only visible body text
        $body_text  = $this->extract_body_text( $this->rendered_html );
        $word_count = str_word_count( $body_text );
        if ( $word_count < 300 ) {
            /* translators: %d: word count */
            $issues[] = [ 'type' => 'warning', 'msg' => sprintf( __('Content too short (%d words). Aim for 600+ words for better ranking.', 'zs-seo-audit-optimization'), $word_count ) ];
        } elseif ( $word_count < 600 ) {
            /* translators: %d: word count */
            $issues[] = [ 'type' => 'info', 'msg' => sprintf( __('Content is okay (%d words) but 600+ words is recommended.', 'zs-seo-audit-optimization'), $word_count ) ];
        } else {
            /* translators: %d: word count */
            $good[] = sprintf( __('Content length is great (%d words).', 'zs-seo-audit-optimization'), $word_count );
        }

        return [ 'issues' => $issues, 'good' => $good ];
    }

    /**
     * Extract visible body text from full HTML, ignoring nav/header/footer/scripts.
     */
    private function extract_body_text( string $html ): string {
        if ( empty( $html ) ) return '';

        // Remove script, style, nav, header, footer tags + content
        $clean = preg_replace( '/<(script|style|nav|header|footer|noscript)[^>]*>.*?<\/\1>/is', '', $html );

        // Extract only <main>, <article>, or <body> content
        if ( preg_match( '/<main[^>]*>(.*?)<\/main>/is', $clean, $m ) ) {
            $clean = $m[1];
        } elseif ( preg_match( '/<article[^>]*>(.*?)<\/article>/is', $clean, $m ) ) {
            $clean = $m[1];
        } elseif ( preg_match( '/<body[^>]*>(.*?)<\/body>/is', $clean, $m ) ) {
            $clean = $m[1];
        }

        return wp_strip_all_tags( $clean );
    }

    // ── Heading Structure — from RENDERED HTML ─────────────────────────────────
    private function analyze_headings(): array {
        $issues   = [];
        $good     = [];
        $headings = [];

        // Use rendered HTML — gets headings from custom theme too
        $html = $this->rendered_html ?: $this->content;

        // Extract only body area (ignore nav/header/footer headings)
        $body_html = $html;
        if ( preg_match( '/<main[^>]*>(.*?)<\/main>/is', $html, $m ) ) {
            $body_html = $m[1];
        } elseif ( preg_match( '/<article[^>]*>(.*?)<\/article>/is', $html, $m ) ) {
            $body_html = $m[1];
        }

        preg_match_all( '/<h([1-6])[^>]*>(.*?)<\/h[1-6]>/is', $body_html, $matches );

        if ( empty( $matches[1] ) ) {
            $issues[] = [ 'type' => 'warning', 'msg' => __('No headings found in page content area. Add H1–H3 headings for SEO structure.', 'zs-seo-audit-optimization') ];
            return [ 'issues' => $issues, 'good' => $good, 'tree' => [] ];
        }

        foreach ( $matches[1] as $i => $level ) {
            $headings[] = [
                'level' => (int) $level,
                'text'  => wp_strip_all_tags( $matches[2][ $i ] ),
            ];
        }

        // H1 count
        $h1s = array_filter( $headings, fn($h) => $h['level'] === 1 );
        if ( count( $h1s ) === 0 ) {
            $issues[] = [ 'type' => 'error', 'msg' => __('No H1 found. Every page needs exactly one H1 tag.', 'zs-seo-audit-optimization') ];
        } elseif ( count( $h1s ) > 1 ) {
            /* translators: %d: heading count */
            $issues[] = [ 'type' => 'error', 'msg' => sprintf( __('Multiple H1 tags found (%d). Use only one H1 per page.', 'zs-seo-audit-optimization'), count( $h1s ) ) ];
        } else {
            /* translators: %s: heading text */
            $good[] = sprintf( __('H1 found: "%s"', 'zs-seo-audit-optimization'), mb_substr( array_values( $h1s )[0]['text'], 0, 60 ) );
        }

        // Heading order check
        $prev = 0;
        foreach ( $headings as $h ) {
            if ( $prev > 0 && $h['level'] > $prev + 1 ) {
                $issues[] = [
                    'type' => 'error',
                    'msg'  => sprintf(
                        /* translators: 1: previous heading level, 2: current heading level, 3: heading text */
                        __('Heading order skipped: H%1$d → H%2$d ("%3$s"). Fix heading hierarchy.', 'zs-seo-audit-optimization'),
                        $prev, $h['level'], mb_substr( $h['text'], 0, 40 )
                    ),
                ];
            }
            $prev = $h['level'];
        }

        if ( count( $issues ) === 0 ) {
            /* translators: %d: heading count */
            $good[] = sprintf( __('Heading hierarchy is correct (%d headings found).', 'zs-seo-audit-optimization'), count( $headings ) );
        }

        return [ 'issues' => $issues, 'good' => $good, 'tree' => $headings ];
    }

    // ── Image Audit — from RENDERED HTML ──────────────────────────────────────
    private function analyze_images(): array {
        $issues = [];
        $good   = [];
        $images = [];

        $html = $this->rendered_html ?: $this->content;
        preg_match_all( '/<img[^>]+>/i', $html, $matches );

        if ( empty( $matches[0] ) ) {
            $good[] = __('No images found on page.', 'zs-seo-audit-optimization');
            return [ 'issues' => $issues, 'good' => $good, 'images' => [] ];
        }

        // Above-fold keywords — these images should NOT have lazy load
        $above_fold_keywords = [ 'logo', 'hero', 'banner', 'favicon', 'brand', 'header', 'site-icon' ];

        foreach ( $matches[0] as $img_tag ) {
            $src    = '';
            $alt    = null;
            $ext    = '';

            if ( preg_match( '/src=["\']([^"\']+)["\']/', $img_tag, $m ) ) {
                $src = $m[1];
                $ext = strtolower( pathinfo( wp_parse_url( $src, PHP_URL_PATH ), PATHINFO_EXTENSION ) );
            }
            if ( preg_match( '/alt=["\']([^"\']*)["\']/', $img_tag, $m ) ) {
                $alt = $m[1];
            }

            $filename     = strtolower( basename( $src ) );
            $is_svg       = ( $ext === 'svg' );
            $is_png       = ( $ext === 'png' );
            $is_above_fold = false;

            // Check if this is an above-fold image (logo, hero, etc.)
            foreach ( $above_fold_keywords as $kw ) {
                if ( strpos( $filename, $kw ) !== false || strpos( strtolower( $img_tag ), $kw ) !== false ) {
                    $is_above_fold = true;
                    break;
                }
            }

            $has_loading = ( strpos( $img_tag, 'loading=' ) !== false );
            $has_width   = ( strpos( $img_tag, 'width=' ) !== false );
            $has_height  = ( strpos( $img_tag, 'height=' ) !== false );

            $img_issues = [];

            // Alt text
            $is_decorative = ( strpos( $img_tag, 'aria-hidden="true"' ) !== false
                            || strpos( $img_tag, "aria-hidden='true'" ) !== false
                            || strpos( $img_tag, 'role="presentation"' ) !== false );

            if ( ! $is_decorative && ( $alt === null || $alt === '' ) ) {
                $img_issues[] = [
                    'msg'     => __('Missing alt text — required for SEO & accessibility.', 'zs-seo-audit-optimization'),
                    'fix_key' => 'img_alt',
                    'fix_tip' => __('Go to ZS_SEO → Images to add alt text in bulk.', 'zs-seo-audit-optimization'),
                    'fix_url' => admin_url( 'admin.php?page=zs-seo-images' ),
                    'pro'     => false,
                ];
            }

            // PNG → WebP
            if ( $is_png ) {
                $img_issues[] = [
                    'msg'     => __('PNG detected — convert to WebP for ~30% smaller file size.', 'zs-seo-audit-optimization'),
                    'fix_key' => 'png_to_webp',
                    'fix_tip' => __('ZS_SEO Pro can auto-convert PNG images to WebP on the fly.', 'zs-seo-audit-optimization'),
                    'pro'     => true,
                ];
            }

            // Lazy load
            if ( ! $is_svg && ! $is_above_fold && ! $has_loading ) {
                $img_issues[] = [
                    'msg'     => __('Missing loading="lazy" — add for below-fold images.', 'zs-seo-audit-optimization'),
                    'fix_key' => 'lazy_load',
                    'fix_tip' => __('ZS_SEO Pro can auto-inject loading="lazy" on all below-fold images.', 'zs-seo-audit-optimization'),
                    'pro'     => true,
                ];
            }

            // Width/height
            if ( ! $is_svg && ( ! $has_width || ! $has_height ) ) {
                $img_issues[] = [
                    'msg'     => __('Missing width/height attributes — causes CLS (layout shift).', 'zs-seo-audit-optimization'),
                    'fix_key' => 'img_dimensions',
                    'fix_tip' => __('ZS_SEO Pro can auto-detect and inject correct image dimensions.', 'zs-seo-audit-optimization'),
                    'pro'     => true,
                ];
            }

            // Determine location: check post content first, then theme files
            $location = '';
            if ( ! empty( $src ) ) {
                $search_filename = basename( strtok( $src, '?' ) );
                if ( ! empty( $search_filename ) && strpos( $this->content, $search_filename ) !== false ) {
                    $location = 'Database (Post Content)';
                } else {
                    $theme_location = $this->find_image_in_theme( $src );
                    if ( $theme_location ) {
                        $location = $theme_location;
                    }
                }
            }

            $images[] = [ 'src' => esc_url( $src ), 'alt' => $alt, 'issues' => $img_issues, 'location' => $location ];

            foreach ( $img_issues as $iss ) {
                $issues[] = [
                    'type'     => 'warning',
                    'msg'      => $iss['msg'] . ' (' . esc_html( $filename ) . ')',
                    'fix_key'  => $iss['fix_key'] ?? '',
                    'fix_tip'  => $iss['fix_tip'] ?? '',
                    'fix_url'  => $iss['fix_url'] ?? '',
                    'pro'      => $iss['pro'] ?? false,
                    'filename' => $filename,
                    'img_src'  => esc_url( $src ),
                    'location' => $location,
                ];
            }
        }

        $clean = count( array_filter( $images, fn($img) => empty( $img['issues'] ) ) );
        if ( $clean > 0 ) {
            /* translators: %d: image count */
            $good[] = sprintf( __('%d image(s) have no issues.', 'zs-seo-audit-optimization'), $clean );
        }

        return [ 'issues' => $issues, 'good' => $good, 'images' => $images ];
    }

    // ── Performance ────────────────────────────────────────────────────────────
    private function analyze_performance(): array {
        $issues = [];
        $good   = [];

        if ( ! has_post_thumbnail( $this->post_id ) ) {
            $issues[] = [ 'type' => 'info', 'msg' => __('No featured image set. Featured image often becomes the LCP element.', 'zs-seo-audit-optimization') ];
        } else {
            $good[] = __('Featured image is set — good for LCP.', 'zs-seo-audit-optimization');
        }

        // Check rendered HTML for render-blocking resources
        $html = $this->rendered_html;
        if ( $html ) {
            // Count render-blocking <link rel="stylesheet"> in <head>
            if ( preg_match( '/<head[^>]*>(.*?)<\/head>/is', $html, $head_m ) ) {
                $head = $head_m[1];
                preg_match_all( '/<link[^>]+rel=["\']stylesheet["\'][^>]*>/i', $head, $css_m );
                $css_count = count( $css_m[0] );
                if ( $css_count > 5 ) {
                    /* translators: %d: CSS file count */
                    $issues[] = [ 'type' => 'warning', 'msg' => sprintf( __('%d CSS files loaded in <head>. Consider combining or deferring non-critical CSS.', 'zs-seo-audit-optimization'), $css_count ) ];
                } else {
                    /* translators: %d: CSS file count */
                    $good[] = sprintf( __('CSS file count is okay (%d files).', 'zs-seo-audit-optimization'), $css_count );
                }

                // Check for preload hints
                $has_preload = ( strpos( $head, 'rel="preload"' ) !== false || strpos( $head, "rel='preload'" ) !== false );
                if ( ! $has_preload ) {
                    $issues[] = [ 'type' => 'info', 'msg' => __('No preload hints found. Add <link rel="preload"> for critical fonts/CSS.', 'zs-seo-audit-optimization') ];
                } else {
                    $good[] = __('Preload hints found — good for LCP performance.', 'zs-seo-audit-optimization');
                }
            }

            // Count render-blocking <script> tags (those without defer or async)
            preg_match_all( '/<script[^>]+src=["\'][^"\']+["\'][^>]*>/i', $html, $js_m );
            $js_count       = count( $js_m[0] );
            $blocking_count = 0;
            foreach ( $js_m[0] as $tag ) {
                $has_defer = stripos( $tag, 'defer' ) !== false;
                $has_async = stripos( $tag, 'async' ) !== false;
                if ( ! $has_defer && ! $has_async ) {
                    $blocking_count++;
                }
            }
            if ( $js_count > 8 ) {
                if ( $blocking_count > 0 ) {
                    $issues[] = [
                        'type'    => 'warning',
                        'msg'     => sprintf(
                            /* translators: 1: JS file count, 2: blocking file count */
                            __('%1$d JS files found (%2$d blocking without defer/async). Too many scripts hurt page speed.', 'zs-seo-audit-optimization'),
                            $js_count,
                            $blocking_count
                        ),
                        'fix_key' => 'js_defer',
                        'fix_tip' => __('ZS_SEO Pro auto-adds defer to all safe scripts via WordPress filter — no file editing needed.', 'zs-seo-audit-optimization'),
                        'pro'     => true,
                    ];
                } else {
                    /* translators: %d: JS file count */
                    $good[] = sprintf( __('%d JS files found but all have defer/async — no blocking scripts!', 'zs-seo-audit-optimization'), $js_count );
                }
            } elseif ( $js_count > 0 && $blocking_count === 0 ) {
                /* translators: %d: JS file count */
                $good[] = sprintf( __('All %d JS files are non-blocking (defer/async) — great!', 'zs-seo-audit-optimization'), $js_count );
            }


            // iframes
            $iframes = substr_count( $html, '<iframe' );
            if ( $iframes > 2 ) {
                /* translators: %d: iframe count */
                $issues[] = [ 'type' => 'warning', 'msg' => sprintf( __('%d iframes found. Consider lazy loading iframes.', 'zs-seo-audit-optimization'), $iframes ) ];
            }
        } else {
            $issues[] = [ 'type' => 'info', 'msg' => __('Could not fetch page for performance analysis. Make sure the page is publicly accessible.', 'zs-seo-audit-optimization') ];
        }

        return [ 'issues' => $issues, 'good' => $good ];
    }

    // ── Accessibility — from RENDERED HTML ─────────────────────────────────────
    private function analyze_accessibility(): array {
        $issues = [];
        $good   = [];

        $html = $this->rendered_html ?: $this->content;

        // Extract body area only
        $body_html = $html;
        if ( preg_match( '/<body[^>]*>(.*?)<\/body>/is', $html, $bm ) ) {
            $body_html = $bm[1];
        }

        preg_match_all( '/<a([^>]*)>(.*?)<\/a>/is', $body_html, $links );

        $empty_links         = 0;
        $missing_aria_links  = 0;
        $missing_aria_examples = [];
        $missing_aria_details = [];

        foreach ( $links[0] as $i => $full_tag ) {
            $attrs     = $links[1][ $i ];
            $inner     = $links[2][ $i ];
            $text      = trim( wp_strip_all_tags( $inner ) );
            $has_aria  = strpos( $attrs, 'aria-label' ) !== false
                      || strpos( $attrs, 'aria-labelledby' ) !== false;
            $has_title = strpos( $attrs, 'title=' ) !== false;
            $is_skip   = strpos( $attrs, 'skip' ) !== false || strpos( $inner, 'skip' ) !== false;
            $is_sr     = strpos( $attrs, 'sr-only' ) !== false || strpos( $inner, 'sr-only' ) !== false;

            if ( $is_skip || $is_sr ) continue;

            if ( $text === '' && ! $has_aria && ! $has_title ) {
                $empty_links++;
            } elseif ( $text !== '' && ! $has_aria ) {
                $missing_aria_links++;
                $href = '';
                if ( preg_match( '/href=["\']([^"\']*)["\']/', $attrs, $hm ) ) {
                    $href = $hm[1];
                }
                $missing_aria_details[] = [
                    'text' => mb_substr( $text, 0, 80 ),
                    'href' => $href,
                ];
                if ( count( $missing_aria_examples ) < 3 ) {
                    $base_href = basename( $href );
                    $missing_aria_examples[] = mb_substr( $text, 0, 40 ) . ( $base_href ? ' (' . $base_href . ')' : '' );
                }
            }
        }

        if ( $empty_links > 0 ) {
            $issues[] = [
                'type'    => 'error',
                /* translators: %d: link count */
                'msg'     => sprintf( __('%d link(s) have NO text and NO aria-label — screen readers cannot read these.', 'zs-seo-audit-optimization'), $empty_links ),
                'fix_key' => 'empty_links',
                'fix_tip' => __('Add visible text or aria-label="Description" to every empty <a> tag.', 'zs-seo-audit-optimization'),
            ];
        } else {
            $good[] = __('No completely empty links found — good!', 'zs-seo-audit-optimization');
        }

        if ( $missing_aria_links > 0 ) {
            $ex = ! empty( $missing_aria_examples ) ? ' e.g. "' . implode( '", "', $missing_aria_examples ) . '"' : '';
            $issues[] = [
                'type'    => 'warning',
                'msg'     => sprintf(
                    /* translators: 1: link count, 2: examples string */
                    __('%1$d link(s) have visible text but no aria-label.%2$s', 'zs-seo-audit-optimization'),
                    $missing_aria_links,
                    $ex
                ),
                'fix_key' => 'missing_aria_label',
                'fix_tip' => __('Add aria-label to all <a> tags. Example: <a href="/services" aria-label="View All Services page">View All Services <span>→</span></a>', 'zs-seo-audit-optimization'),
                'pro'     => true,
                'details' => $missing_aria_details,
            ];
        } else {
            $good[] = __('All links have aria-label — great for screen readers!', 'zs-seo-audit-optimization');
        }

        // Buttons
        preg_match_all( '/<button([^>]*)>(.*?)<\/button>/is', $body_html, $btns );
        $empty_btns = 0;
        foreach ( $btns[0] as $i => $btn_tag ) {
            $text     = trim( wp_strip_all_tags( $btns[2][ $i ] ) );
            $has_aria = strpos( $btns[1][ $i ], 'aria-label' ) !== false;
            if ( $text === '' && ! $has_aria ) $empty_btns++;
        }
        if ( $empty_btns > 0 ) {
            $issues[] = [
                'type'    => 'error',
                /* translators: %d: button count */
                'msg'     => sprintf( __('%d button(s) have no text or aria-label.', 'zs-seo-audit-optimization'), $empty_btns ),
                'fix_key' => 'empty_buttons',
                'fix_tip' => __('Add aria-label="Button action" to every icon-only <button>.', 'zs-seo-audit-optimization'),
            ];
        }

        // Lang
        if ( strpos( $html, '<html' ) !== false ) {
            if ( strpos( $html, ' lang=' ) === false ) {
                $issues[] = [
                    'type'    => 'warning',
                    'msg'     => __('Missing lang attribute on <html> tag.', 'zs-seo-audit-optimization'),
                    'fix_key' => 'missing_lang',
                    'fix_tip' => __('Add lang="en" to <html> tag in your theme header.php.', 'zs-seo-audit-optimization'),
                ];
            } else {
                $good[] = __('HTML lang attribute is set — good!', 'zs-seo-audit-optimization');
            }
        }

        // Images
        preg_match_all( '/<img([^>]*)>/i', $body_html, $imgs );
        $missing_alt_count = 0;
        foreach ( $imgs[1] as $iattrs ) {
            $has_alt       = preg_match( '/ alt=["\'\'][^\"\']*/i', $iattrs );
            $is_decorative = strpos( $iattrs, 'aria-hidden' ) !== false
                          || strpos( $iattrs, 'role="presentation"' ) !== false;
            if ( ! $has_alt && ! $is_decorative ) $missing_alt_count++;
        }
        if ( $missing_alt_count > 0 ) {
            $issues[] = [
                'type'    => 'warning',
                /* translators: %d: image count */
                'msg'     => sprintf( __('%d image(s) missing alt text on this page.', 'zs-seo-audit-optimization'), $missing_alt_count ),
                'fix_key' => 'missing_img_alt',
                'fix_tip' => __('Go to ZS_SEO → Images to fix all image alt texts in bulk.', 'zs-seo-audit-optimization'),
                'fix_url' => admin_url( 'admin.php?page=zs-seo-images' ),
            ];
        }

        // Tables
        if ( strpos( $body_html, '<table' ) !== false && strpos( $body_html, 'scope=' ) === false ) {
            $issues[] = [
                'type'    => 'warning',
                'msg'     => __('Table(s) without scope on <th> elements.', 'zs-seo-audit-optimization'),
                'fix_key' => 'table_scope',
                'fix_tip' => __('Add scope="col" or scope="row" to all <th> elements.', 'zs-seo-audit-optimization'),
            ];
        }

        if ( count( $issues ) === 0 ) {
            $good[] = __('No accessibility issues found — great!', 'zs-seo-audit-optimization');
        }

        return [ 'issues' => $issues, 'good' => $good ];
    }

    // ── GEO — AI Search Optimization ──────────────────────────────────────────
    private function analyze_geo(): array {
        $issues = [];
        $good   = [];

        $faqs = get_post_meta( $this->post_id, '_zs_seo_faqs', true );
        if ( empty( $faqs ) ) {
            $issues[] = [ 'type' => 'info', 'msg' => __('No FAQs added. FAQ schema helps ChatGPT, Gemini, Google AI Overview cite your page.', 'zs-seo-audit-optimization') ];
        } else {
            /* translators: %d: FAQ count */
            $good[] = sprintf( __('%d FAQ(s) with JSON-LD schema — great for GEO / AI search.', 'zs-seo-audit-optimization'), count( $faqs ) );
        }

        // Check rendered HTML for FAQ-like content
        $body_text = $this->extract_body_text( $this->rendered_html ?: $this->content );

        $has_qa = preg_match( '/\?/s', $body_text );
        if ( $has_qa ) {
            $good[] = __('Question marks detected in content — good for Q&A / AI answer engines.', 'zs-seo-audit-optimization');
        } else {
            $issues[] = [ 'type' => 'info', 'msg' => __('No Q&A patterns found. Add question-based headings and answers for better AI visibility.', 'zs-seo-audit-optimization') ];
        }

        // Check JSON-LD exists in rendered HTML
        if ( strpos( $this->rendered_html, 'application/ld+json' ) !== false ) {
            $good[] = __('JSON-LD schema detected on page — great for AI search engines!', 'zs-seo-audit-optimization');
        } else {
            $issues[] = [ 'type' => 'warning', 'msg' => __('No JSON-LD schema found on page. Enable Schema in ZS_SEO Settings.', 'zs-seo-audit-optimization') ];
        }

        $definitions = preg_match_all( '/is (a|an|the) /i', $body_text );
        if ( $definitions >= 2 ) {
            $good[] = __('Definition-style sentences found — AI engines love this!', 'zs-seo-audit-optimization');
        } else {
            $issues[] = [ 'type' => 'info', 'msg' => __('Add sentences like "ZS Creations is a digital agency..." — AI engines prefer direct definitions.', 'zs-seo-audit-optimization') ];
        }

        return [ 'issues' => $issues, 'good' => $good ];
    }
}
