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

class VMO_Website_Final_QA_Gate {
    private static $instance = null;

    public static function get_instance() {
        if (!self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function evaluate($user_id, $session_id, $website_payload, array $business_package = array(), array $context = array()) {
        if (empty($context['canonical_generation_context']) && !empty($context['visual_preset']) && function_exists('vmo_get_visual_preset_registry')) {
            $preset = vmo_get_visual_preset_registry()->get($context['visual_preset']);
            $context['canonical_generation_context'] = array(
                'selected_visual_preset' => $preset['key'],
                'visual_preset_label' => $preset['label'],
                'visual_preset' => $preset,
                'required_premium_signals' => $preset['required_signals'],
            );
            $context['required_premium_signals'] = $preset['required_signals'];
        }

        $pages = $this->extract_pages($website_payload);
        $html = $this->build_combined_content($pages);
        $html_lower = strtolower($html);
        $text = strtolower(wp_strip_all_tags($html));
        $critical = array();
        $warnings = array();

        $media_manifest = $this->build_media_manifest($html, $context);
        if (!$media_manifest['media_complete']) {
            $critical[] = 'Unresolved media generation markers remain in the draft.';
        }
        $product_packet_gate = $this->evaluate_ecommerce_product_packets($context, $business_package);
        if (!empty($product_packet_gate['required']) && empty($product_packet_gate['ready'])) {
            $critical[] = 'Ecommerce preview is blocked until every product has a complete product content packet and product media packet.';
            foreach ($product_packet_gate['missing'] as $missing_packet) {
                if (!is_array($missing_packet)) {
                    continue;
                }
                $product_label = sanitize_text_field((string) ($missing_packet['product'] ?? 'Product'));
                $content_missing = is_array($missing_packet['content_missing'] ?? null) ? implode(', ', $missing_packet['content_missing']) : '';
                $media_missing = is_array($missing_packet['media_missing'] ?? null) ? implode(', ', $missing_packet['media_missing']) : '';
                $critical[] = trim('Product packet incomplete for ' . $product_label . '. Missing content: ' . ($content_missing ?: 'none') . '. Missing media: ' . ($media_missing ?: 'none') . '.');
            }
        }

        if (preg_match('/lorem ipsum|your business|sample text|replace this|insert .* here/i', $text)) {
            $critical[] = 'Generic placeholder copy is still present.';
        }
        if (preg_match('/placeholder\.com|picsum\.photos|example\.com|via\.placeholder/i', $html_lower)) {
            $critical[] = 'Placeholder media or example URLs are still present.';
        }
        if (preg_match('/data-vmo-internal-fallback|vmo-internal-fallback|vmo-fallback-page|data-vmo-preview-blocked/i', $html_lower)) {
            $critical[] = 'Internal fallback markup is present; fallback pages cannot be exposed as final preview output.';
        }
        if (preg_match('/<\?php|\beval\s*\(|document\.cookie|wp-config\.php/i', $html)) {
            $critical[] = 'Unsafe executable or sensitive code pattern detected in generated markup.';
        }

        $preset_alignment_score = $this->score_preset_alignment($html_lower, $context, $warnings);
        $benchmark_score = $this->score_benchmark_standard($html_lower, $text, $media_manifest, $context, $business_package, $warnings, $critical);

        $dimensions = array(
            'strategic_fit' => $this->score_strategy($text, $business_package, $warnings),
            'visual_premium_quality' => $this->score_premium_visuals($html_lower, $text, $warnings),
            'hero_quality' => $this->score_hero($html_lower, $warnings),
            'preset_alignment' => $preset_alignment_score,
            'benchmark_standard' => $benchmark_score,
            'media_completeness' => $this->score_media($media_manifest, $warnings),
            'mobile_experience' => $this->score_mobile($html_lower, $warnings),
            'navigation_simplicity' => $this->score_navigation($html_lower, $warnings),
            'conversion_clarity' => $this->score_conversion($html_lower, $text, $warnings),
            'seo_foundation' => $this->score_seo($html_lower, $business_package, $warnings),
            'dashboard_kpi_wiring' => $this->score_dashboard_kpi_wiring($html_lower, $context, $warnings),
            'ecommerce_product_packets' => !empty($product_packet_gate['required']) ? (!empty($product_packet_gate['ready']) ? 100 : 20) : 100,
            'operational_integration' => $this->score_operational_integration($html_lower, $business_package, $warnings),
            'performance_readiness' => $this->score_performance($html_lower, $warnings),
            'accessibility' => $this->score_accessibility($html_lower, $media_manifest, $warnings),
            'wordpress_safety' => $this->score_safety($html_lower, $critical, $warnings),
            'standalone_portability' => $this->score_standalone_portability($html_lower, $critical, $warnings),
            'wow_factor' => $this->score_wow_factor($html_lower, $media_manifest, $context, $warnings),
        );

        $thresholds = $this->category_minimums();
        $threshold_failures = $this->threshold_failures($dimensions, $thresholds);
        foreach ($threshold_failures as $category => $failure) {
            $warnings[] = $failure['label'] . ' is below the v2.0 minimum (' . $failure['score'] . '/' . $failure['minimum'] . ').';
        }

        foreach (array('media_completeness', 'hero_quality', 'visual_premium_quality', 'mobile_experience', 'navigation_simplicity', 'conversion_clarity', 'preset_alignment', 'benchmark_standard', 'dashboard_kpi_wiring', 'ecommerce_product_packets', 'wordpress_safety', 'standalone_portability') as $critical_category) {
            if (isset($threshold_failures[$critical_category])) {
                $critical[] = $threshold_failures[$critical_category]['label'] . ' failed a critical v2.0 threshold.';
            }
        }

        $score = (int) round(array_sum($dimensions) / max(1, count($dimensions)));
        $score = max(0, min(100, $score - (count($critical) * 8)));

        $thresholds_passed = empty($threshold_failures);
        $preview_ready = $media_manifest['media_complete'] && empty($critical) && $thresholds_passed;
        $publish_ready = $preview_ready;

        return array(
            'gate' => 'vmo_website_final_qa_gate',
            'version' => '2026-04-30',
            'user_id' => (int) $user_id,
            'session_id' => (string) $session_id,
            'score' => $score,
            'grade' => $this->grade($score),
            'preview_ready' => $preview_ready,
            'publish_ready' => $publish_ready,
            'critical_findings' => array_values(array_unique($critical)),
            'warnings' => array_values(array_unique($warnings)),
            'dimensions' => $dimensions,
            'thresholds' => $thresholds,
            'threshold_failures' => $threshold_failures,
            'media_manifest' => $media_manifest,
            'ecommerce_product_packet_gate' => $product_packet_gate,
            'selected_visual_preset' => sanitize_key((string) ($context['visual_preset'] ?? ($context['canonical_generation_context']['selected_visual_preset'] ?? ''))),
            'checklist' => array(
                'final_qa_passed' => $publish_ready,
                'v2_thresholds_passed' => $thresholds_passed,
                'premium_visual_ready' => ($dimensions['visual_premium_quality'] ?? 0) >= ($thresholds['visual_premium_quality'] ?? 90),
                'hero_quality_ready' => ($dimensions['hero_quality'] ?? 0) >= ($thresholds['hero_quality'] ?? 90),
                'preset_alignment_ready' => ($dimensions['preset_alignment'] ?? 0) >= ($thresholds['preset_alignment'] ?? 90),
                'benchmark_standard_ready' => ($dimensions['benchmark_standard'] ?? 0) >= ($thresholds['benchmark_standard'] ?? 90),
                'mobile_experience_ready' => ($dimensions['mobile_experience'] ?? 0) >= ($thresholds['mobile_experience'] ?? 90),
                'navigation_ready' => ($dimensions['navigation_simplicity'] ?? 0) >= ($thresholds['navigation_simplicity'] ?? 90),
                'conversion_ready' => ($dimensions['conversion_clarity'] ?? 0) >= ($thresholds['conversion_clarity'] ?? 90),
                'dashboard_kpi_ready' => ($dimensions['dashboard_kpi_wiring'] ?? 0) >= ($thresholds['dashboard_kpi_wiring'] ?? 85),
                'product_content_packets_complete' => empty($product_packet_gate['required']) || !empty($product_packet_gate['content_packets_complete']),
                'product_media_packets_complete' => empty($product_packet_gate['required']) || !empty($product_packet_gate['media_packets_complete']),
                'ecommerce_product_generation_ready' => empty($product_packet_gate['required']) || !empty($product_packet_gate['ready']),
                'operational_integration_ready' => ($dimensions['operational_integration'] ?? 0) >= ($thresholds['operational_integration'] ?? 85),
                'media_complete' => $media_manifest['media_complete'],
                'wordpress_safe' => ($dimensions['wordpress_safety'] ?? 0) >= ($thresholds['wordpress_safety'] ?? 95),
                'standalone_portable' => ($dimensions['standalone_portability'] ?? 0) >= ($thresholds['standalone_portability'] ?? 95),
            ),
            'summary' => $publish_ready
                ? 'Final QA gate passed. The website is ready for explicit publish approval.'
                : 'Final QA gate found issues that should be resolved before publishing.',
        );
    }

    public function merge_with_existing_report($existing_report, array $final_gate) {
        $existing_report = is_array($existing_report) ? $existing_report : array();
        $has_existing_publish_gate = array_key_exists('publish_ready', $existing_report);
        $existing_score = isset($existing_report['score']) && is_numeric($existing_report['score']) ? (int) $existing_report['score'] : 100;
        $final_score = isset($final_gate['score']) && is_numeric($final_gate['score']) ? (int) $final_gate['score'] : 0;

        $merged = $existing_report;
        $merged['score'] = min($existing_score, $final_score);
        $merged['publish_ready'] = $has_existing_publish_gate
            ? (!empty($existing_report['publish_ready']) && !empty($final_gate['publish_ready']))
            : !empty($final_gate['publish_ready']);
        $merged['preview_ready'] = !empty($final_gate['preview_ready']);
        $merged['final_qa_gate'] = $final_gate;

        $merged['critical_findings'] = array_values(array_unique(array_merge(
            is_array($existing_report['critical_findings'] ?? null) ? $existing_report['critical_findings'] : array(),
            is_array($final_gate['critical_findings'] ?? null) ? $final_gate['critical_findings'] : array()
        )));
        $merged['warnings'] = array_values(array_unique(array_merge(
            is_array($existing_report['warnings'] ?? null) ? $existing_report['warnings'] : array(),
            is_array($final_gate['warnings'] ?? null) ? $final_gate['warnings'] : array()
        )));

        $checklist = is_array($existing_report['checklist'] ?? null) ? $existing_report['checklist'] : array();
        $existing_media_complete = array_key_exists('media_complete', $checklist) ? !empty($checklist['media_complete']) : null;
        $final_checklist = is_array($final_gate['checklist'] ?? null) ? $final_gate['checklist'] : array();
        foreach ($final_checklist as $key => $value) {
            $checklist[$key] = $value;
        }
        if ($existing_media_complete !== null) {
            $checklist['media_complete'] = $existing_media_complete && !empty($final_checklist['media_complete']);
        }
        $merged['checklist'] = $checklist;
        $merged['summary'] = $merged['publish_ready']
            ? 'Website QA passed, including the final premium experience gate.'
            : 'Website QA found blockers or premium experience gaps before publishing.';

        return $merged;
    }

    private function extract_pages($website_payload) {
        if (is_string($website_payload)) {
            return array('home' => $website_payload);
        }
        if (!is_array($website_payload)) {
            return array();
        }

        $pages = array();
        foreach (array('pages_map', 'pages', 'pages_generated') as $map_key) {
            if (!is_array($website_payload[$map_key] ?? null)) {
                continue;
            }
            foreach ($website_payload[$map_key] as $key => $value) {
                $slug = is_string($key) ? sanitize_title($key) : 'page-' . count($pages);
                $pages[$slug] = $this->value_to_html($value);
            }
        }
        foreach (array('content', 'homepage_html', 'website_code') as $content_key) {
            if (!empty($website_payload[$content_key]) && is_string($website_payload[$content_key])) {
                $pages['home'] = $website_payload[$content_key];
                break;
            }
        }

        return $pages;
    }

    private function value_to_html($value) {
        if (is_string($value)) {
            return $value;
        }
        if (is_array($value)) {
            if (!empty($value['html']) && is_string($value['html'])) {
                return $value['html'];
            }
            if (!empty($value['content']) && is_string($value['content'])) {
                return $value['content'];
            }
            return wp_json_encode($value);
        }
        return '';
    }

    private function build_combined_content(array $pages) {
        return implode("\n", array_map('strval', $pages));
    }

    private function build_media_manifest($html, array $context = array()) {
        if (function_exists('vmo_get_website_media_manifest_service')) {
            return vmo_get_website_media_manifest_service()->build_from_html($html, $context);
        }

        $unresolved_images = (int) preg_match_all('/data-vmo-generate\s*=|\[(generated_image):/i', $html);
        $unresolved_videos = (int) preg_match_all('/data-vmo-sora\s*=|\[(generated_video):/i', $html);
        $resolved_images = (int) preg_match_all('/<img\b/i', $html);
        $resolved_videos = (int) preg_match_all('/<video\b|<iframe\b/i', $html);
        $images_missing_alt = (int) preg_match_all('/<img\b(?![^>]*\balt\s*=)/i', $html);
        return array(
            'media_complete' => ($unresolved_images + $unresolved_videos) === 0,
            'unresolved_image_markers' => $unresolved_images,
            'unresolved_video_markers' => $unresolved_videos,
            'resolved_image_tags' => $resolved_images,
            'resolved_video_tags' => $resolved_videos,
            'resolved_visual_assets' => $resolved_images + $resolved_videos,
            'alt_text_complete' => $resolved_images > 0 && $images_missing_alt === 0,
            'queueable_placeholders' => (int) preg_match_all('/vmo-media-placeholder|data-vmo-generate|data-vmo-sora/i', $html),
        );
    }

    private function evaluate_ecommerce_product_packets(array $context, array $business_package) {
        $summary = $this->product_packet_summary_from_context($context, $business_package);
        $required = !empty($summary['product_count']) || $this->context_requires_ecommerce_packets($context, $business_package);
        if (!$required) {
            return array(
                'required' => false,
                'ready' => true,
                'content_packets_complete' => true,
                'media_packets_complete' => true,
                'product_count' => 0,
                'missing' => array(),
            );
        }

        $products = is_array($summary['products'] ?? null) ? $summary['products'] : array();
        $missing = is_array($summary['missing'] ?? null) ? $summary['missing'] : array();
        $content_complete = !empty($summary['content_packets_complete']);
        $media_complete = !empty($summary['media_packets_complete']);

        if (!$products) {
            $content_complete = false;
            $media_complete = false;
            $missing[] = array(
                'product' => 'Catalog',
                'content_missing' => array('product_content_packet'),
                'media_missing' => array('product_media_packet'),
            );
        }

        foreach ($products as $product) {
            if (!is_array($product)) {
                continue;
            }
            if (empty($product['content_packet_complete'])) {
                $content_complete = false;
            }
            if (empty($product['media_packet_complete'])) {
                $media_complete = false;
            }
        }

        return array(
            'required' => true,
            'ready' => $content_complete && $media_complete && !empty($products),
            'content_packets_complete' => $content_complete,
            'media_packets_complete' => $media_complete,
            'product_count' => count($products),
            'required_content_fields' => is_array($summary['required_content_fields'] ?? null) ? $summary['required_content_fields'] : array(),
            'required_media_slots' => is_array($summary['required_media_slots'] ?? null) ? $summary['required_media_slots'] : array(),
            'missing' => array_values($missing),
            'hard_requirement' => 'complete_product_content_packet_and_product_media_packet_before_ecommerce_preview',
        );
    }

    private function product_packet_summary_from_context(array $context, array $business_package) {
        $candidates = array(
            $context['product_generation_packets'] ?? null,
            $context['business_data']['ecommerce']['product_generation_packets'] ?? null,
            $business_package['product_generation_packets'] ?? null,
            $business_package['ecommerce']['product_generation_packets'] ?? null,
        );
        foreach ($candidates as $candidate) {
            if (is_array($candidate) && (!empty($candidate['products']) || !empty($candidate['product_count']))) {
                return $candidate;
            }
        }
        return array();
    }

    private function context_requires_ecommerce_packets(array $context, array $business_package) {
        $ecommerce = is_array($context['business_data']['ecommerce'] ?? null) ? $context['business_data']['ecommerce'] : array();
        $package_ecommerce = is_array($business_package['ecommerce'] ?? null) ? $business_package['ecommerce'] : array();
        foreach (array($ecommerce, $package_ecommerce) as $candidate) {
            if (!is_array($candidate) || !$candidate) {
                continue;
            }
            if (!empty($candidate['products']) || !empty($candidate['intends_to_sell']) || !empty($candidate['sell_on_website']) || !empty($candidate['collect_payments'])) {
                return true;
            }
        }
        return false;
    }

    private function category_minimums() {
        return array(
            'strategic_fit' => 85,
            'visual_premium_quality' => 90,
            'hero_quality' => 90,
            'preset_alignment' => 90,
            'benchmark_standard' => 90,
            'media_completeness' => 95,
            'mobile_experience' => 90,
            'navigation_simplicity' => 90,
            'conversion_clarity' => 90,
            'seo_foundation' => 85,
            'dashboard_kpi_wiring' => 85,
            'ecommerce_product_packets' => 100,
            'operational_integration' => 85,
            'performance_readiness' => 82,
            'accessibility' => 85,
            'wordpress_safety' => 95,
            'standalone_portability' => 95,
            'wow_factor' => 88,
        );
    }

    private function threshold_failures(array $dimensions, array $thresholds) {
        $failures = array();
        foreach ($thresholds as $category => $minimum) {
            $score = (int) ($dimensions[$category] ?? 0);
            if ($score < $minimum) {
                $failures[$category] = array(
                    'label' => ucwords(str_replace('_', ' ', $category)),
                    'score' => $score,
                    'minimum' => (int) $minimum,
                );
            }
        }
        return $failures;
    }

    private function score_strategy($text, array $business_package, array &$warnings) {
        $score = 72;
        foreach (array('audience', 'customer', 'trust', 'proof', 'outcome', 'benefit', 'story', 'why', 'choose') as $signal) {
            if (strpos($text, $signal) !== false) {
                $score += 3;
            }
        }
        if (str_word_count($text) >= 350) {
            $score += 8;
        } else {
            $warnings[] = 'Strategic fit is limited because page copy is thin for a consultation-backed website.';
        }
        if (!empty($business_package['business_brief_summary']) || !empty($business_package['strategy']) || !empty($business_package['market_analysis'])) {
            $score += 8;
        }
        return max(0, min(100, $score));
    }

    private function score_hero($html_lower, array &$warnings) {
        $score = 35;
        if (strpos($html_lower, 'hero') !== false || strpos($html_lower, 'min-h-screen') !== false || strpos($html_lower, '100vh') !== false) {
            $score += 18;
        } else {
            $warnings[] = 'Hero quality is weak because a clear hero scene was not detected.';
        }
        if (preg_match('/<img\b|<video\b|background-image|data-vmo-generate|data-vmo-sora/i', $html_lower)) {
            $score += 16;
        } else {
            $warnings[] = 'Hero quality is weak because no focal hero media was detected.';
        }
        if (preg_match('/book|buy|shop|order|reserve|schedule|get started|contact|call/i', $html_lower)) {
            $score += 12;
        }
        $layer_hits = 0;
        foreach (array('layer', 'background', 'foreground', 'subject', 'overlay', 'parallax', 'glass', 'floating', 'z-index') as $signal) {
            if (strpos($html_lower, $signal) !== false) {
                $layer_hits++;
            }
        }
        if ($layer_hits >= 3) {
            $score += 24;
        } else {
            $warnings[] = 'Hero scene does not show enough layering signals for the v2.0 standard.';
        }
        return max(0, min(100, $score));
    }

    private function score_premium_visuals($html_lower, $text, array &$warnings) {
        $score = 30;
        $signals = array('hero', 'min-h-screen', '90vh', '100vh', 'data-vmo-animate', 'backdrop-filter', 'glass', 'parallax', 'sticky', 'swiper', 'gsap', 'transform', 'hover:', 'section');
        foreach ($signals as $signal) {
            if (strpos($html_lower, $signal) !== false) {
                $score += 5;
            }
        }
        $section_count = (int) preg_match_all('/<section\b/i', $html_lower);
        if ($section_count >= 5) {
            $score += 10;
        } else {
            $warnings[] = 'Premium visual score is limited because the page has fewer than five semantic sections.';
        }
        if (str_word_count($text) < 250) {
            $warnings[] = 'Website copy may be too thin for a premium, business-specific experience.';
            $score -= 10;
        }
        return max(0, min(100, $score));
    }

    private function score_media(array $manifest, array &$warnings) {
        if (!$manifest['media_complete']) {
            return !empty($manifest['media_floor_met']) ? 55 : 35;
        }
        $score = 88;
        if (($manifest['resolved_visual_assets'] ?? (($manifest['resolved_image_tags'] ?? 0) + ($manifest['resolved_video_tags'] ?? 0))) >= 3) {
            $score += 5;
        } else {
            $warnings[] = 'Media manifest has too few resolved visual assets for a premium generated website.';
        }
        if (!empty($manifest['alt_text_complete'])) {
            $score += 4;
        }
        if (!empty($manifest['placement_map_required']) && !empty($manifest['placement_map'])) {
            $score += 3;
        }
        return max(0, min(100, $score));
    }

    private function score_navigation($html_lower, array &$warnings) {
        $score = 55;
        foreach (array('<nav', 'menu', 'href=', 'aria-label', 'button', 'cta', 'sticky', 'fixed', 'z-index') as $signal) {
            if (strpos($html_lower, $signal) !== false) {
                $score += 6;
            }
        }
        if ($score < 90) {
            $warnings[] = 'Navigation simplicity needs stronger menu, accessibility, and CTA-forward signals.';
        }
        return max(0, min(100, $score));
    }

    private function score_seo($html_lower, array $business_package, array &$warnings) {
        $score = 55;
        foreach (array('<title', 'meta name="description', "meta name='description", 'schema.org', 'application/ld+json', 'canonical', 'alt=', '<h1', '<h2') as $signal) {
            if (strpos($html_lower, $signal) !== false) {
                $score += 6;
            }
        }
        if (!empty($business_package['seo_strategy']) || !empty($business_package['seo_roadmap'])) {
            $score += 10;
        }
        if ($score < 85) {
            $warnings[] = 'SEO foundation does not yet show enough metadata, schema, heading, alt text, or roadmap signals.';
        }
        return max(0, min(100, $score));
    }

    private function score_dashboard_kpi_wiring($html_lower, array $context, array &$warnings) {
        $score = 40;
        if (strpos($html_lower, 'vmotrackevent') !== false || strpos($html_lower, 'data-vmo-track') !== false || strpos($html_lower, 'vmo_track') !== false) {
            $score += 35;
        }
        if (!empty($context['canonical_generation_context']['v2_agent_plan']['agents']['website_kpi_wiring'])) {
            $score += 45;
        }
        if (!empty($context['operations_manifest']['dashboard']['connected'])) {
            $score += 15;
        }
        if ($score < 85) {
            $warnings[] = 'Dashboard/KPI wiring contract is not visible enough for v2.0 review readiness.';
        }
        return max(0, min(100, $score));
    }

    private function score_operational_integration($html_lower, array $business_package, array &$warnings) {
        $score = 55;
        foreach (array('<form', 'mailto:', 'tel:', 'map', 'booking', 'appointment', 'reservation', 'checkout', 'cart', 'vmo_buy_product', 'shipping', 'order') as $signal) {
            if (strpos($html_lower, $signal) !== false) {
                $score += 5;
            }
        }
        if (!empty($business_package['operating_state']) || !empty($business_package['recommended_systems'])) {
            $score += 10;
        }
        if ($score < 85) {
            $warnings[] = 'Operational integration needs clearer forms, payments, booking, location, shipping, or system wiring.';
        }
        return max(0, min(100, $score));
    }

    private function score_performance($html_lower, array &$warnings) {
        $score = 90;
        $script_count = (int) preg_match_all('/<script\b/i', $html_lower);
        if ($script_count > 6) {
            $score -= 12;
            $warnings[] = 'Performance readiness is reduced by a high inline/script count.';
        }
        if (strpos($html_lower, 'loading="lazy"') !== false || strpos($html_lower, "loading='lazy'") !== false) {
            $score += 5;
        }
        if (strpos($html_lower, 'will-change') !== false) {
            $score -= 4;
        }
        return max(0, min(100, $score));
    }

    private function score_accessibility($html_lower, array $manifest, array &$warnings) {
        $score = 62;
        foreach (array('aria-', 'alt=', 'role=', 'label', 'prefers-reduced-motion', 'focus', 'button') as $signal) {
            if (strpos($html_lower, $signal) !== false) {
                $score += 6;
            }
        }
        if (!empty($manifest['alt_text_complete'])) {
            $score += 6;
        }
        if (strpos($html_lower, 'prefers-reduced-motion') === false) {
            $warnings[] = 'Reduced-motion fallback is required for v2.0 motion readiness.';
        }
        return max(0, min(100, $score));
    }

    private function score_wow_factor($html_lower, array $manifest, array $context, array &$warnings) {
        $score = 45;
        foreach (array('parallax', 'glass', 'backdrop-filter', 'video', 'swiper', 'gsap', 'scrolltrigger', 'sticky', 'layer', 'floating', 'cinematic', 'interactive') as $signal) {
            if (strpos($html_lower, $signal) !== false) {
                $score += 5;
            }
        }
        if (!empty($manifest['media_complete']) && ($manifest['resolved_visual_assets'] ?? 0) >= 2) {
            $score += 8;
        }
        if (!empty($context['canonical_generation_context']['selected_visual_preset'])) {
            $score += 7;
        }
        if ($score < 88) {
            $warnings[] = 'Wow factor is below the v2.0 premium benchmark.';
        }
        return max(0, min(100, $score));
    }

    private function score_mobile($html_lower, array &$warnings) {
        $score = 45;
        foreach (array('@media', 'sm:', 'md:', 'lg:', 'clamp(', 'max-width', 'grid', 'flex', 'viewport') as $signal) {
            if (strpos($html_lower, $signal) !== false) {
                $score += 7;
            }
        }
        if ($score < 70) {
            $warnings[] = 'Mobile readiness signals are weak; generated markup should include responsive sizing and breakpoints.';
        }
        return max(0, min(100, $score));
    }

    private function score_content($text, array $business_package, array &$warnings) {
        $score = 80;
        if (str_word_count($text) < 250) {
            $score -= 20;
        }
        $business_name = strtolower((string) ($business_package['business_overview']['business_name'] ?? $business_package['business_name'] ?? ''));
        if ($business_name !== '' && strpos($text, $business_name) === false) {
            $warnings[] = 'The final QA gate could not verify the business name in page copy.';
            $score -= 10;
        }
        if (preg_match('/we help businesses|what we do|our services|your trusted partner/i', $text)) {
            $warnings[] = 'Some copy still reads generic and should be more consultation-specific.';
            $score -= 8;
        }
        return max(0, min(100, $score));
    }

    private function score_conversion($html_lower, $text, array &$warnings) {
        $score = 50;
        foreach (array('<nav', 'href=', '<footer', 'contact', 'book', 'schedule', 'buy now', 'shop now', 'get started', 'call', 'mailto:', '<form') as $signal) {
            if (strpos($html_lower . ' ' . $text, $signal) !== false) {
                $score += 5;
            }
        }
        if ($score < 75) {
            $warnings[] = 'Navigation or conversion actions are not strong enough for publish-ready output.';
        }
        return max(0, min(100, $score));
    }

    private function score_safety($html_lower, array &$critical, array &$warnings) {
        $score = 100;
        if (strpos($html_lower, '<script') !== false) {
            $score -= 10;
            $warnings[] = 'Inline script detected; verify it is required and export-safe.';
        }
        if (preg_match('/<\?php|\beval\s*\(|document\.cookie|wp-config\.php/i', $html_lower)) {
            $score = 0;
        }
        return max(0, min(100, $score));
    }

    private function score_standalone_portability($html_lower, array &$critical, array &$warnings) {
        $score = 100;
        $forbidden_patterns = array(
            '/admin-ajax\.php|\/wp-json\/|wp-content\/plugins|wp-content\/themes|wp-includes/i' => 'WordPress runtime URL detected in generated website markup.',
            '/window\.wp\b|wp\.apifetch|wp_enqueue|wpnonce|_wpnonce|\bwp_ajax\b/i' => 'WordPress JavaScript/API dependency detected in generated website markup.',
            '/<\?php|wp-config\.php/i' => 'PHP or WordPress configuration reference detected in generated website markup.',
        );
        foreach ($forbidden_patterns as $pattern => $message) {
            if (preg_match($pattern, $html_lower)) {
                $score -= 35;
                $critical[] = $message;
            }
        }

        if (preg_match_all('/(^|[\s>])\[(?!generated_image|generated_video)([a-z][a-z0-9_:-]*)(?:\s+[^\]]*)?\](?:\s*\[\/\2\])?/i', $html_lower, $shortcode_matches, PREG_SET_ORDER)) {
            foreach ($shortcode_matches as $shortcode_match) {
                $shortcode_name = strtolower((string) ($shortcode_match[2] ?? ''));
                if ($shortcode_name !== '' && strpos($shortcode_name, 'data-') !== 0 && strpos($shortcode_name, 'aria-') !== 0) {
                    $score -= 30;
                    $critical[] = 'Shortcode-style markup detected; generated websites must export as plain HTML/CSS/JS.';
                    break;
                }
            }
        }
        if (preg_match('/<form\b/i', $html_lower) && !preg_match('/\b(action|data-vmo-webhook|data-endpoint|mailto:)\b/i', $html_lower)) {
            $score -= 12;
            $warnings[] = 'Form markup needs a portable submission endpoint before standalone export.';
        }
        if (preg_match('/vmo_ajax|ajaxurl|rest_url|home_url\(/i', $html_lower)) {
            $score -= 20;
            $critical[] = 'Generated markup references builder-only globals or WordPress URL helpers.';
        }

        return max(0, min(100, $score));
    }

    private function score_benchmark_standard($html_lower, $text, array $manifest, array $context, array $business_package, array &$warnings, array &$critical) {
        $checks = array();
        $context_signal = strtolower((string) wp_json_encode($context) . ' ' . (string) wp_json_encode($business_package) . ' ' . $text . ' ' . $html_lower);

        $layer_hits = 0;
        foreach (array('layer', 'background', 'foreground', 'subject', 'overlay', 'parallax', 'glass', 'floating', 'z-index', 'depth') as $signal) {
            if (strpos($html_lower, $signal) !== false) {
                $layer_hits++;
            }
        }

        $checks[] = array(
            'label' => 'three-layer hero scene',
            'passed' => (strpos($html_lower, 'hero') !== false || strpos($html_lower, 'min-h-screen') !== false || strpos($html_lower, '100vh') !== false) && $layer_hits >= 3,
        );
        $checks[] = array(
            'label' => 'hero focal subject',
            'passed' => (bool) preg_match('/hero[\s\S]{0,1800}(<img\b|<video\b|data-vmo-generate|data-vmo-sora|background-image|focal|subject|product|food|bottle|interface|venue|suite|model)/i', $html_lower),
        );
        $checks[] = array(
            'label' => 'premium menu selected',
            'passed' => (bool) preg_match('/<nav\b/i', $html_lower) && (bool) preg_match('/aria-label|sticky|fixed|glass|overlay|floating|frosted|capsule|bottom-pill|menu_cta|data-vmo-menu-preset/i', $html_lower),
        );
        $checks[] = array(
            'label' => 'media complete',
            'passed' => !empty($manifest['media_complete']),
        );
        $checks[] = array(
            'label' => 'no visible placeholders',
            'passed' => !preg_match('/lorem ipsum|your business|sample text|replace this|insert .* here|placeholder\.com|picsum\.photos|example\.com|via\.placeholder/i', $html_lower . ' ' . $text),
        );
        $checks[] = array(
            'label' => 'mobile-specific version',
            'passed' => (bool) preg_match('/@media|sm:|md:|lg:|max-width|viewport|clamp\(|data-vmo-mobile|mobile-/i', $html_lower),
        );

        $blocked_cta = (bool) preg_match('/(cta|button|vmo-btn|buy|reserve|book|order)[^{}<>]{0,180}pointer-events\s*:\s*none|pointer-events\s*:\s*none[^{}<>]{0,180}(cta|button|vmo-btn|buy|reserve|book|order)/i', $html_lower);
        $has_conversion_action = (bool) preg_match('/book|buy|shop|order|reserve|schedule|get started|contact|call|mailto:|tel:|data-vmo-track/i', $html_lower . ' ' . $text);
        $checks[] = array(
            'label' => 'CTA layers unblocked',
            'passed' => $has_conversion_action && !$blocked_cta,
        );
        if ($blocked_cta) {
            $critical[] = 'CTA or purchase layers appear to be blocked by pointer-events rules.';
        }

        $required_signals = $this->get_required_preset_signals($context);
        $selected_preset = sanitize_key((string) ($context['visual_preset'] ?? ($context['canonical_generation_context']['selected_visual_preset'] ?? '')));
        $preset_match_count = $this->count_required_signal_matches($html_lower, $required_signals);
        $preset_reflected = $selected_preset !== '' && strpos($html_lower, $selected_preset) !== false;
        if (!$preset_reflected && !empty($required_signals)) {
            $preset_reflected = $preset_match_count >= max(1, (int) ceil(count($required_signals) * 0.9));
        }
        $checks[] = array(
            'label' => 'visual preset reflected in output',
            'passed' => $preset_reflected,
        );

        $resolved_visual_assets = (int) ($manifest['resolved_visual_assets'] ?? (($manifest['resolved_image_tags'] ?? 0) + ($manifest['resolved_video_tags'] ?? 0)));
        $media_heavy = $resolved_visual_assets >= 5 || (int) ($manifest['resolved_video_tags'] ?? 0) > 0 || preg_match('/<video\b|data-vmo-sora/i', $html_lower);
        if ($media_heavy) {
            $checks[] = array(
                'label' => 'boot or preloader for media-heavy experience',
                'passed' => (bool) preg_match('/preloader|boot-screen|boot_to_hero|loading-screen|skeleton|poster=/i', $html_lower),
            );
        }

        $is_ecommerce = (bool) preg_match('/ecommerce|e-commerce|shop|store|retail|cart|checkout|buy now|price|wine|fashion|apparel|food order/i', $context_signal);
        if ($is_ecommerce) {
            $checks[] = array(
                'label' => 'ecommerce storytelling instead of basic grid',
                'passed' => (bool) preg_match('/data-vmo-ecommerce-preset|product-story|product_story|quick-view|quick view|click-to-detail|product-detail|detail-panel|story-led|scroll-reveal-product|ingredient-float|lookbook|order-slider|flavor-selector/i', $html_lower),
            );
            $checks[] = array(
                'label' => 'product click-to-detail path',
                'passed' => (bool) preg_match('/click-to-detail|quick-view|quick view|detail-panel|product-detail|aria-expanded|data-product-detail|view details/i', $html_lower),
            );
        }

        if (preg_match('/restaurant|food|burger|cafe|bar |bakery|catering|menu|reservation|order/i', $context_signal)) {
            $checks[] = array(
                'label' => 'restaurant or food appetite visuals',
                'passed' => (bool) preg_match('/food-hero|appetite|dish|menu|reservation|order|burger|chef|cuisine|venue|table/i', $html_lower),
            );
        }

        if (preg_match('/wine|winery|vineyard|bottle|tasting|distillery|brewery/i', $context_signal)) {
            $checks[] = array(
                'label' => 'wine/product click-to-detail storytelling',
                'passed' => (bool) preg_match('/wine_click_to_detail|click-to-detail|tasting|bottle|vineyard|origin|notes|pairing|reserve/i', $html_lower),
            );
        }

        if (preg_match('/fashion|apparel|clothing|lookbook|beauty|lifestyle/i', $context_signal)) {
            $checks[] = array(
                'label' => 'fashion editorial lookbook behavior',
                'passed' => (bool) preg_match('/fashion_lookbook|lookbook|editorial|collection|model|runway|quick-view|style/i', $html_lower),
            );
        }

        if (preg_match('/saas|software|app|platform|dashboard|technology|demo/i', $context_signal)) {
            $checks[] = array(
                'label' => 'SaaS or product demo interaction',
                'passed' => (bool) preg_match('/product-demo|product_demo|interface|dashboard|workflow|demo|platform|feature-proof|screen|scroll-led/i', $html_lower),
            );
        }

        if (preg_match('/hospitality|hotel|resort|suite|luxury stay|guest|room|venue/i', $context_signal)) {
            $checks[] = array(
                'label' => 'luxury hospitality booking experience',
                'passed' => (bool) preg_match('/hospitality|hotel|resort|suite|booking|reservation|guest|room|concierge|full-bleed/i', $html_lower),
            );
        }

        if (preg_match('/fullscreen|full-screen|mini-site|minisite|campaign|immersive/i', $context_signal)) {
            $checks[] = array(
                'label' => 'full-screen mini-site mode',
                'passed' => (bool) preg_match('/fullscreen|full-screen|100vh|min-h-screen|section-snap|snap|progress|panel-menu|mini-site|minisite/i', $html_lower),
            );
        }

        $passed_count = 0;
        $failed_labels = array();
        foreach ($checks as $check) {
            if (!empty($check['passed'])) {
                $passed_count++;
            } else {
                $failed_labels[] = $check['label'];
            }
        }

        if (!empty($failed_labels)) {
            $warnings[] = 'V2.0 benchmark gaps: ' . implode(', ', array_slice($failed_labels, 0, 6)) . '.';
        }

        return (int) round(($passed_count / max(1, count($checks))) * 100);
    }

    private function get_required_preset_signals(array $context) {
        $required = array();
        if (is_array($context['canonical_generation_context']['required_premium_signals'] ?? null)) {
            $required = $context['canonical_generation_context']['required_premium_signals'];
        } elseif (is_array($context['required_premium_signals'] ?? null)) {
            $required = $context['required_premium_signals'];
        }

        if (empty($required) && function_exists('vmo_get_visual_preset_registry')) {
            $preset_key = sanitize_key((string) ($context['visual_preset'] ?? ($context['canonical_generation_context']['selected_visual_preset'] ?? '')));
            if ($preset_key !== '') {
                $preset = vmo_get_visual_preset_registry()->get($preset_key);
                if (is_array($preset['required_signals'] ?? null)) {
                    $required = $preset['required_signals'];
                }
            }
        }

        return array_values(array_filter(array_map('strval', $required)));
    }

    private function count_required_signal_matches($html_lower, array $required) {
        $matched = 0;
        $allowed_short_parts = array('app', 'buy', 'cta', 'kpi', 'map', 'seo');

        foreach ($required as $signal) {
            $parts = preg_split('/[^a-z0-9]+/i', strtolower((string) $signal));
            foreach ($parts as $part) {
                if ($part === '') {
                    continue;
                }
                if ((strlen($part) >= 4 || in_array($part, $allowed_short_parts, true)) && strpos($html_lower, $part) !== false) {
                    $matched++;
                    break;
                }
            }
        }

        return $matched;
    }

    private function score_preset_alignment($html_lower, array $context, array &$warnings) {
        $required = $this->get_required_preset_signals($context);
        if (empty($required)) {
            $warnings[] = 'No canonical visual preset requirements were available for final QA.';
            return 0;
        }

        $matched = $this->count_required_signal_matches($html_lower, $required);

        $score = (int) round(($matched / max(1, count($required))) * 100);
        if ($score < 90) {
            $warnings[] = 'Generated markup does not strongly reflect the selected premium visual preset.';
        }
        return max(0, min(100, $score));
    }

    private function grade($score) {
        if ($score >= 90) return 'A';
        if ($score >= 82) return 'B';
        if ($score >= 70) return 'C';
        if ($score >= 55) return 'D';
        return 'F';
    }
}

if (!function_exists('vmo_get_website_final_qa_gate')) {
    function vmo_get_website_final_qa_gate() {
        return VMO_Website_Final_QA_Gate::get_instance();
    }
}