<?php
declare(strict_types=1);

namespace Help4BuilderSuite\Modules\SEO;

use Help4BuilderSuite\Support\BusinessProfileDefaults;
use WP_Post;

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

final class IntentScorer
{
    private CommerceDiscovery $commerce;

    public function __construct(?CommerceDiscovery $commerce = null)
    {
        $this->commerce = $commerce ?? new CommerceDiscovery();
    }

    /**
     * @return array<string, array<string, mixed>>
     */
    public function profileCatalog(): array
    {
        $profiles = [
            'general' => [
                'label' => __('General website', 'help4-builder-suite'),
                'description' => __('Balanced search, readability, conversion, and page-quality guidance.', 'help4-builder-suite'),
                'weights' => ['search' => 40, 'readability' => 25, 'analytics' => 20, 'adsense' => 15, 'intent' => 0],
            ],
            'local' => [
                'label' => __('Local or service business', 'help4-builder-suite'),
                'description' => __('Prioritizes business identity, locations, contact paths, service areas, hours, and local relevance.', 'help4-builder-suite'),
                'weights' => ['search' => 30, 'readability' => 15, 'analytics' => 20, 'adsense' => 5, 'intent' => 30],
            ],
            'ecommerce' => [
                'label' => __('Online store or catalog', 'help4-builder-suite'),
                'description' => __('Prioritizes product identity, landing pages, images, price, availability, variants, policies, and merchant data.', 'help4-builder-suite'),
                'weights' => ['search' => 30, 'readability' => 10, 'analytics' => 20, 'adsense' => 0, 'intent' => 40],
            ],
            'publisher' => [
                'label' => __('Publisher or knowledge site', 'help4-builder-suite'),
                'description' => __('Prioritizes useful depth, authorship, structure, citations, internal discovery, and readable editorial content.', 'help4-builder-suite'),
                'weights' => ['search' => 30, 'readability' => 25, 'analytics' => 10, 'adsense' => 20, 'intent' => 15],
            ],
            'global' => [
                'label' => __('International or multilingual', 'help4-builder-suite'),
                'description' => __('Prioritizes localized URLs, language alternates, canonical alignment, international previews, and global discovery.', 'help4-builder-suite'),
                'weights' => ['search' => 35, 'readability' => 20, 'analytics' => 10, 'adsense' => 10, 'intent' => 25],
            ],
        ];
        $filtered = apply_filters('h4bs_seo_intent_profiles', $profiles);
        if (! is_array($filtered)) {
            return $profiles;
        }

        $clean = [];
        foreach ($filtered as $key => $profile) {
            $key = sanitize_key((string) $key);
            if ($key === '' || ! is_array($profile)) {
                continue;
            }
            $weights = is_array($profile['weights'] ?? null) ? $profile['weights'] : [];
            $normalized = [];
            foreach (['search', 'readability', 'analytics', 'adsense', 'intent'] as $bucket) {
                $normalized[$bucket] = max(0, min(100, (int) ($weights[$bucket] ?? 0)));
            }
            if (array_sum($normalized) <= 0) {
                continue;
            }
            $clean[$key] = [
                'label' => sanitize_text_field((string) ($profile['label'] ?? ucfirst($key))),
                'description' => sanitize_text_field((string) ($profile['description'] ?? '')),
                'weights' => $normalized,
            ];
        }
        return $clean !== [] ? $clean : $profiles;
    }

    /**
     * @param array<string, mixed> $settings
     */
    public function resolveProfile(WP_Post $post, array $settings): string
    {
        $profiles = $this->profileCatalog();
        $explicit = sanitize_key((string) get_post_meta($post->ID, '_h4_seo_intent_profile', true));
        if (isset($profiles[$explicit])) {
            return $explicit;
        }
        $default = sanitize_key((string) ($settings['seo_default_intent_profile'] ?? 'auto'));
        if (isset($profiles[$default])) {
            return $default;
        }

        $schema = sanitize_key((string) get_post_meta($post->ID, '_h4_seo_schema_type', true));
        $content = strtolower((string) $post->post_content);
        if ($schema === 'product' || str_contains($content, '[h4_products') || str_contains($content, '[h4_checkout')) {
            return 'ecommerce';
        }
        if ($post->post_type === 'h4_location' || in_array($schema, ['localbusiness', 'realestatelisting', 'singlefamilyresidence', 'apartment', 'house'], true)) {
            return 'local';
        }
        $hreflang = get_post_meta($post->ID, '_h4_seo_hreflang', true);
        if (is_array($hreflang) && count($hreflang) > 1) {
            return 'global';
        }
        if ($post->post_type === 'post' || in_array($schema, ['article', 'blogposting', 'newsarticle'], true)) {
            return 'publisher';
        }

        $businessProfile = BusinessProfileDefaults::selectedSlug();
        if (in_array($businessProfile, ['local_service', 'contractor', 'healthcare', 'realtor', 'restaurant'], true)) {
            return 'local';
        }
        if (in_array($businessProfile, ['store', 'membership'], true)) {
            return 'ecommerce';
        }
        if ($businessProfile === 'content') {
            return 'publisher';
        }
        return 'general';
    }

    /**
     * @param array<string, mixed> $base
     * @param array<string, mixed> $settings
     * @return array<string, mixed>
     */
    public function enrich(WP_Post $post, array $base, array $settings): array
    {
        $profile = $this->resolveProfile($post, $settings);
        $catalog = $this->profileCatalog();
        $profileData = $catalog[$profile] ?? $catalog['general'];
        $checks = match ($profile) {
            'local' => $this->localChecks($post),
            'ecommerce' => $this->ecommerceChecks($post, $settings),
            'publisher' => $this->publisherChecks($post),
            'global' => $this->globalChecks($post, $settings),
            default => $this->generalChecks($post, $base),
        };
        $intentScore = $this->checksScore($checks);
        $scores = is_array($base['scores'] ?? null) ? $base['scores'] : [];
        $scores['intent'] = $intentScore;
        $weights = is_array($profileData['weights'] ?? null) ? $profileData['weights'] : [];
        $weights = apply_filters('h4bs_seo_score_formula', $weights, $profile, $post, $base, $checks);
        $weights = is_array($weights) ? $weights : [];
        $weightTotal = 0;
        $weighted = 0.0;
        foreach (['search', 'readability', 'analytics', 'adsense', 'intent'] as $bucket) {
            $weight = max(0, min(100, (int) ($weights[$bucket] ?? 0)));
            $weightTotal += $weight;
            $weighted += max(0, min(100, (int) ($scores[$bucket] ?? 0))) * $weight;
            $weights[$bucket] = $weight;
        }
        $score = $weightTotal > 0 ? (int) round($weighted / $weightTotal) : (int) ($base['score'] ?? 0);
        $actions = $this->intentActions($checks);
        $warnings = is_array($base['warnings'] ?? null) ? $base['warnings'] : [];
        foreach ($actions as $action) {
            $warnings[] = (string) ($action['fix'] ?? '');
        }

        $base['score'] = max(0, min(100, $score));
        $base['label'] = $this->scoreLabel((int) $base['score']);
        $base['scores'] = $scores;
        $base['warnings'] = array_values(array_unique(array_filter(array_map('strval', $warnings))));
        $base['intent_profile'] = $profile;
        $base['intent_profile_label'] = (string) ($profileData['label'] ?? ucfirst($profile));
        $base['intent_profile_description'] = (string) ($profileData['description'] ?? '');
        $base['intent_checks'] = $checks;
        $base['intent_actions'] = $actions;
        $base['score_formula'] = $weights;

        $filtered = apply_filters('h4bs_seo_intent_scorecard', $base, $post, $settings);
        return is_array($filtered) ? $filtered : $base;
    }

    /** @return array<int, array<string, mixed>> */
    private function localChecks(WP_Post $post): array
    {
        $settings = get_option('h4bs_local_seo', []);
        $settings = is_array($settings) ? $settings : [];
        $locationIds = get_posts([
            'post_type' => 'h4_location',
            'post_status' => 'publish',
            'numberposts' => 50,
            'fields' => 'ids',
            'orderby' => 'menu_order title',
            'order' => 'ASC',
        ]);
        $locationId = $post->post_type === 'h4_location'
            ? $post->ID
            : absint($settings['primary_location_id'] ?? ($locationIds[0] ?? 0));
        if ($locationId <= 0 && isset($locationIds[0])) {
            $locationId = absint($locationIds[0]);
        }
        $location = $locationId > 0 ? get_post_meta($locationId, '_h4_local_data', true) : [];
        $location = is_array($location) ? $location : [];
        $businessName = trim((string) ($settings['business_name'] ?? get_bloginfo('name')));
        $businessType = sanitize_text_field((string) ($location['business_type'] ?? ($settings['business_type'] ?? 'LocalBusiness')));
        $phone = trim((string) ($location['phone'] ?? ($settings['phone'] ?? '')));
        $addressCount = 0;
        foreach (['address_line_1', 'city', 'region', 'postal_code', 'country_code'] as $field) {
            if (trim((string) ($location[$field] ?? '')) !== '') {
                $addressCount++;
            }
        }
        $serviceAreas = array_values(array_filter(array_map('strval', (array) ($location['service_areas'] ?? []))));
        $hasCoordinates = is_numeric($location['latitude'] ?? null) && is_numeric($location['longitude'] ?? null);
        $hasHours = false;
        foreach ((array) ($location['opening_hours'] ?? []) as $hours) {
            if (! is_array($hours) || ! empty($hours['closed'])) {
                continue;
            }
            if (! empty($hours['open_24h']) || ((string) ($hours['open_1'] ?? '') !== '' && (string) ($hours['close_1'] ?? '') !== '')) {
                $hasHours = true;
                break;
            }
        }
        $haystack = strtolower(wp_strip_all_tags(get_the_title($post) . ' ' . $post->post_content . ' ' . $post->post_excerpt));
        $areaTerms = array_merge([(string) ($location['city'] ?? ''), (string) ($location['region'] ?? '')], $serviceAreas);
        $hasLocalRelevance = $post->post_type === 'h4_location';
        foreach ($areaTerms as $area) {
            $area = strtolower(trim($area));
            if ($area !== '' && str_contains($haystack, $area)) {
                $hasLocalRelevance = true;
                break;
            }
        }
        $hasConversion = trim((string) get_post_meta($post->ID, '_h4_seo_primary_cta', true)) !== ''
            || trim((string) get_post_meta($post->ID, '_h4_seo_conversion_goal', true)) !== ''
            || preg_match('/(?:tel:|\[h4_form|\[contact-form|request a quote|book|call|contact)/i', (string) $post->post_content) === 1;

        return [
            $this->check('business_identity', __('Business identity', 'help4-builder-suite'), $businessName !== '' ? 10 : 0, 10, __('Add the real public business name in Local SEO.', 'help4-builder-suite')),
            $this->check('business_subtype', __('Specific business type', 'help4-builder-suite'), $businessType !== '' && $businessType !== 'LocalBusiness' ? 8 : 0, 8, __('Choose the most specific truthful LocalBusiness subtype.', 'help4-builder-suite')),
            $this->check('phone', __('Local contact number', 'help4-builder-suite'), $phone !== '' ? 10 : 0, 10, __('Add the public phone number used consistently across the site and business listings.', 'help4-builder-suite')),
            $this->check('location_page', __('Published location page', 'help4-builder-suite'), $locationId > 0 ? 10 : 0, 10, __('Publish at least one complete Help4 location page.', 'help4-builder-suite')),
            $this->check('address', __('Address or service-area coverage', 'help4-builder-suite'), $addressCount >= 4 ? 14 : ($serviceAreas !== [] ? 7 : 0), 14, __('Complete the location address, or define truthful service areas for a service-area business.', 'help4-builder-suite')),
            $this->check('coordinates', __('Map coordinates', 'help4-builder-suite'), $hasCoordinates ? 8 : ($serviceAreas !== [] ? 4 : 0), 8, __('Add verified latitude and longitude for each physical location.', 'help4-builder-suite')),
            $this->check('hours', __('Opening hours', 'help4-builder-suite'), $hasHours ? 10 : 0, 10, __('Add regular hours and keep holiday exceptions current.', 'help4-builder-suite')),
            $this->check('service_area', __('Service areas', 'help4-builder-suite'), $serviceAreas !== [] ? 8 : 0, 8, __('List the cities, neighborhoods, or regions this location genuinely serves.', 'help4-builder-suite')),
            $this->check('local_relevance', __('Page and place relevance', 'help4-builder-suite'), $hasLocalRelevance ? 12 : 0, 12, __('Explain the service and location naturally on this page; do not mass-produce thin city pages.', 'help4-builder-suite')),
            $this->check('local_conversion', __('Local conversion path', 'help4-builder-suite'), $hasConversion ? 10 : 0, 10, __('Add a clear call, booking, quote, directions, or contact action.', 'help4-builder-suite')),
        ];
    }

    /**
     * @param array<string, mixed> $settings
     * @return array<int, array<string, mixed>>
     */
    private function ecommerceChecks(WP_Post $post, array $settings): array
    {
        $audit = $this->commerce->catalogAudit(500);
        $coverage = is_array($audit['coverage'] ?? null) ? $audit['coverage'] : [];
        $sampled = (int) ($audit['counts']['sampled'] ?? 0);
        $schema = sanitize_key((string) get_post_meta($post->ID, '_h4_seo_schema_type', true));
        $content = strtolower((string) $post->post_content);
        $hasCatalogSurface = str_contains($content, '[h4_products') || str_contains($content, '[h4_checkout');
        $commerceProductId = absint(get_post_meta($post->ID, '_h4_seo_commerce_product_id', true));
        $mappedProduct = $schema === 'product' ? $this->commerce->productById($commerceProductId, $settings) : null;
        $productSurfacePoints = $hasCatalogSurface || is_array($mappedProduct) ? 12 : ($schema === 'product' ? 6 : 0);
        $returnPolicy = esc_url_raw((string) ($settings['commerce_seo_return_policy_url'] ?? ''), ['http', 'https']);
        $shippingPolicy = esc_url_raw((string) ($settings['commerce_seo_shipping_policy_url'] ?? ''), ['http', 'https']);
        $feedEnabled = rest_sanitize_boolean($settings['commerce_seo_feed_enabled'] ?? '0');

        return [
            $this->check('product_surface', __('Product-focused page', 'help4-builder-suite'), $productSurfacePoints, 12, __('Use a Help4 catalog or checkout block, or map Product schema to the matching active Help4 Commerce product ID.', 'help4-builder-suite')),
            $this->coverageCheck('product_description', __('Product descriptions', 'help4-builder-suite'), (int) ($coverage['description'] ?? 0), 14, __('Give every product an original, factual description instead of manufacturer boilerplate alone.', 'help4-builder-suite'), $sampled),
            $this->coverageCheck('product_image', __('Product images', 'help4-builder-suite'), (int) ($coverage['image'] ?? 0), 12, __('Add a clear primary image for every product and accurate image alt text on the landing page.', 'help4-builder-suite'), $sampled),
            $this->coverageCheck('product_url', __('Crawlable landing URLs', 'help4-builder-suite'), (int) ($coverage['landing_url'] ?? 0), 12, __('Give each product a stable HTTP(S) landing URL that shows the matching price and availability.', 'help4-builder-suite'), $sampled),
            $this->coverageCheck('product_identifier', __('Product identifiers', 'help4-builder-suite'), (int) ($coverage['identifier'] ?? 0), 12, __('Add a valid GTIN when assigned; otherwise use truthful brand, MPN, and SKU data.', 'help4-builder-suite'), $sampled),
            $this->coverageCheck('product_brand', __('Product brands', 'help4-builder-suite'), (int) ($coverage['brand'] ?? 0), 8, __('Set each product brand or a truthful store-wide default brand.', 'help4-builder-suite'), $sampled),
            $this->coverageCheck('price_currency', __('Price and currency', 'help4-builder-suite'), (int) ($coverage['price_currency'] ?? 0), 10, __('Keep the visible landing-page price and ISO currency synchronized with product data.', 'help4-builder-suite'), $sampled),
            $this->coverageCheck('availability', __('Availability', 'help4-builder-suite'), (int) ($coverage['availability'] ?? 0), 8, __('Keep stock, preorder, and backorder status accurate in the catalog and landing page.', 'help4-builder-suite'), $sampled),
            $this->coverageCheck('variants', __('Variant identity', 'help4-builder-suite'), (int) ($coverage['variants'] ?? 0), 6, __('Give every variant a unique SKU and explicit size, color, material, or other differentiating attributes.', 'help4-builder-suite'), $sampled),
            $this->check('merchant_policies', __('Shipping and return policies', 'help4-builder-suite'), ($returnPolicy !== '' ? 2 : 0) + ($shippingPolicy !== '' ? 2 : 0), 4, __('Link clear public shipping and return policies from Help4 settings.', 'help4-builder-suite')),
            $this->check('merchant_feed', __('Merchant discovery feed', 'help4-builder-suite'), $feedEnabled ? 2 : 0, 2, __('Enable the merchant feed after its catalog readiness report is accurate.', 'help4-builder-suite')),
        ];
    }

    /** @return array<int, array<string, mixed>> */
    private function publisherChecks(WP_Post $post): array
    {
        $plain = wp_strip_all_tags((string) $post->post_content);
        $wordCount = str_word_count($plain);
        $headingCount = preg_match_all('/<h[2-6]\b/i', (string) $post->post_content);
        $linkCount = preg_match_all('/<a\b[^>]+href=/i', (string) $post->post_content);
        $authorId = (int) $post->post_author;
        $authorBio = trim((string) get_the_author_meta('description', $authorId));
        $credentials = trim((string) get_user_meta($authorId, '_h4_seo_credentials', true));
        $schema = sanitize_key((string) get_post_meta($post->ID, '_h4_seo_schema_type', true));
        $hasImage = has_post_thumbnail($post) || trim((string) get_post_meta($post->ID, '_h4_seo_social_image_url', true)) !== '';
        $taxonomyCount = count(wp_get_post_categories($post->ID)) + count(wp_get_post_tags($post->ID));

        return [
            $this->check('article_schema', __('Editorial schema', 'help4-builder-suite'), in_array($schema, ['article', 'blogposting', 'newsarticle'], true) ? 15 : 0, 15, __('Choose the truthful Article, BlogPosting, or NewsArticle schema type.', 'help4-builder-suite')),
            $this->check('authorship', __('Visible authorship', 'help4-builder-suite'), $authorBio !== '' ? 10 : 0, 10, __('Add a real author biography that explains who created the content.', 'help4-builder-suite')),
            $this->check('credentials', __('Relevant expertise', 'help4-builder-suite'), $credentials !== '' ? 5 : 0, 5, __('Add only real credentials or expertise that matter to this topic.', 'help4-builder-suite')),
            $this->check('editorial_depth', __('Useful editorial depth', 'help4-builder-suite'), $wordCount >= 600 ? 20 : ($wordCount >= 300 ? 12 : 0), 20, __('Add original examples, evidence, steps, screenshots, or analysis where the topic needs them.', 'help4-builder-suite')),
            $this->check('editorial_structure', __('Scannable structure', 'help4-builder-suite'), $headingCount >= 2 ? 10 : ($headingCount === 1 ? 5 : 0), 10, __('Use descriptive headings that help readers scan the article.', 'help4-builder-suite')),
            $this->check('editorial_links', __('Useful references and pathways', 'help4-builder-suite'), $linkCount >= 2 ? 10 : ($linkCount === 1 ? 5 : 0), 10, __('Add useful internal links and cite primary sources when claims need evidence.', 'help4-builder-suite')),
            $this->check('editorial_image', __('Shareable editorial image', 'help4-builder-suite'), $hasImage ? 10 : 0, 10, __('Add a representative featured or social image with accurate alternative text.', 'help4-builder-suite')),
            $this->check('taxonomy', __('Topic organization', 'help4-builder-suite'), $taxonomyCount > 0 ? 10 : 0, 10, __('Assign a useful category and only genuinely helpful topic tags.', 'help4-builder-suite')),
            $this->check('publication_dates', __('Publication history', 'help4-builder-suite'), $post->post_date_gmt !== '' && $post->post_modified_gmt !== '' ? 5 : 0, 5, __('Keep publish and meaningful update dates accurate.', 'help4-builder-suite')),
            $this->check('editorial_excerpt', __('Article summary', 'help4-builder-suite'), trim((string) $post->post_excerpt) !== '' ? 5 : 0, 5, __('Write a useful excerpt that summarizes the article without clickbait.', 'help4-builder-suite')),
        ];
    }

    /**
     * @param array<string, mixed> $settings
     * @return array<int, array<string, mixed>>
     */
    private function globalChecks(WP_Post $post, array $settings): array
    {
        $alternates = get_post_meta($post->ID, '_h4_seo_hreflang', true);
        $alternates = is_array($alternates) ? $alternates : [];
        $localizedCount = count(array_diff_key($alternates, ['x-default' => true]));
        $canonical = trim((string) get_post_meta($post->ID, '_h4_seo_canonical', true));
        $socialImage = trim((string) get_post_meta($post->ID, '_h4_seo_social_image_url', true));
        $socialAlt = trim((string) get_post_meta($post->ID, '_h4_seo_social_image_alt', true));
        $sameAs = array_values(array_filter((array) ($settings['same_as_urls'] ?? []), 'is_string'));
        $indexNow = rest_sanitize_boolean($settings['indexnow_enabled'] ?? '0');
        $public = (string) get_option('blog_public', '1') === '1';

        return [
            $this->check('localized_urls', __('Localized URL set', 'help4-builder-suite'), $localizedCount >= 2 ? 25 : ($localizedCount === 1 ? 12 : 0), 25, __('Add reciprocal hreflang URLs for every real language or regional version.', 'help4-builder-suite')),
            $this->check('x_default', __('Default language destination', 'help4-builder-suite'), isset($alternates['x-default']) ? 8 : 0, 8, __('Add x-default for the language selector or global fallback page.', 'help4-builder-suite')),
            $this->check('global_canonical', __('Canonical alignment', 'help4-builder-suite'), $canonical === '' || filter_var($canonical, FILTER_VALIDATE_URL) ? 10 : 0, 10, __('Use a valid self-canonical for each localized page.', 'help4-builder-suite')),
            $this->check('document_language', __('Document language', 'help4-builder-suite'), get_locale() !== '' ? 10 : 0, 10, __('Set the correct WordPress site language for this localized site.', 'help4-builder-suite')),
            $this->check('locale_preview', __('Localized social preview', 'help4-builder-suite'), $socialImage !== '' && $socialAlt !== '' ? 12 : ($socialImage !== '' ? 6 : 0), 12, __('Add a localized social image and meaningful image description.', 'help4-builder-suite')),
            $this->check('global_identity', __('Global organization identity', 'help4-builder-suite'), $sameAs !== [] ? 10 : 0, 10, __('Link verified public organization profiles with sameAs settings.', 'help4-builder-suite')),
            $this->check('fast_discovery', __('Fast change discovery', 'help4-builder-suite'), $indexNow ? 10 : 0, 10, __('Enable IndexNow for supported engines after the production domain is public.', 'help4-builder-suite')),
            $this->check('global_sitemap', __('Public sitemap discovery', 'help4-builder-suite'), $public ? 15 : 0, 15, __('Keep production indexing enabled and submit the WordPress sitemap in each verified webmaster account.', 'help4-builder-suite')),
        ];
    }

    /**
     * @param array<string, mixed> $base
     * @return array<int, array<string, mixed>>
     */
    private function generalChecks(WP_Post $post, array $base): array
    {
        $scores = is_array($base['scores'] ?? null) ? $base['scores'] : [];
        return [
            $this->check('general_search', __('Search-result foundation', 'help4-builder-suite'), (int) round(((int) ($scores['search'] ?? 0)) * 0.4), 40, __('Complete the title, description, canonical, schema, and social preview.', 'help4-builder-suite')),
            $this->check('general_readability', __('Readable page', 'help4-builder-suite'), (int) round(((int) ($scores['readability'] ?? 0)) * 0.25), 25, __('Make the copy easy to scan and understand.', 'help4-builder-suite')),
            $this->check('general_conversion', __('Measurable next step', 'help4-builder-suite'), (int) round(((int) ($scores['analytics'] ?? 0)) * 0.2), 20, __('Give the page a clear action and measurable conversion goal.', 'help4-builder-suite')),
            $this->check('general_quality', __('Page quality', 'help4-builder-suite'), (int) round(((int) ($scores['adsense'] ?? 0)) * 0.15), 15, __('Add original substance, evidence, or useful supporting media.', 'help4-builder-suite')),
        ];
    }

    private function coverageCheck(string $key, string $label, int $percent, int $possible, string $fix, int $sampled): array
    {
        $percent = max(0, min(100, $percent));
        $earned = $sampled > 0 ? (int) round($possible * ($percent / 100)) : 0;
        $check = $this->check($key, $label, $earned, $possible, $fix);
        $check['coverage_percent'] = $percent;
        $check['sampled_products'] = $sampled;
        return $check;
    }

    /** @return array<string, mixed> */
    private function check(string $key, string $label, int $earned, int $possible, string $fix): array
    {
        $possible = max(1, $possible);
        $earned = max(0, min($possible, $earned));
        return [
            'key' => sanitize_key($key),
            'label' => sanitize_text_field($label),
            'earned' => $earned,
            'possible' => $possible,
            'status' => $earned >= $possible ? 'pass' : ($earned > 0 ? 'warning' : 'fail'),
            'fix' => sanitize_text_field($fix),
        ];
    }

    /** @param array<int, array<string, mixed>> $checks */
    private function checksScore(array $checks): int
    {
        $earned = 0;
        $possible = 0;
        foreach ($checks as $check) {
            $earned += max(0, (int) ($check['earned'] ?? 0));
            $possible += max(0, (int) ($check['possible'] ?? 0));
        }
        return $possible > 0 ? (int) round(($earned / $possible) * 100) : 0;
    }

    /**
     * @param array<int, array<string, mixed>> $checks
     * @return array<int, array{title: string, fix: string}>
     */
    private function intentActions(array $checks): array
    {
        usort($checks, static function (array $a, array $b): int {
            $aPossible = max(1, (int) ($a['possible'] ?? 1));
            $bPossible = max(1, (int) ($b['possible'] ?? 1));
            return (((int) ($a['earned'] ?? 0)) / $aPossible) <=> (((int) ($b['earned'] ?? 0)) / $bPossible);
        });
        $actions = [];
        foreach ($checks as $check) {
            if ((string) ($check['status'] ?? '') === 'pass') {
                continue;
            }
            $actions[] = [
                'title' => (string) ($check['label'] ?? ''),
                'fix' => (string) ($check['fix'] ?? ''),
            ];
            if (count($actions) >= 3) {
                break;
            }
        }
        return $actions;
    }

    private function scoreLabel(int $score): string
    {
        if ($score >= 90) {
            return __('Ready', 'help4-builder-suite');
        }
        if ($score >= 80) {
            return __('Strong', 'help4-builder-suite');
        }
        if ($score >= 65) {
            return __('Needs polish', 'help4-builder-suite');
        }
        return __('Needs work', 'help4-builder-suite');
    }
}
