<?php
/**
 * Benchmark comparison helpers — pure functions that turn the hub's
 * `Seoforge_Intel_Client::benchmarks()` payload into render-ready comparison
 * data for the dashboard widgets (task P3-B: the visible payoff of opting
 * into the SEO network).
 *
 * Absent or malformed benchmark data always resolves to `null` so a caller
 * can render exactly today's UI unchanged — this class never invents a
 * number and never implies a verdict, only a purely descriptive position
 * relative to the peer group.
 *
 * @package Brainwerk SEO Suite
 */

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

class Seoforge_Benchmark {

    /** KPI metrics the hub may carry a p25/p50/p75 breakdown for. */
    const METRICS = array(
        'bounce_pct', 'pages_per_session', 'avg_session_sec', 'engaged_pct',
        'conv_rate', 'source_search_pct', 'source_ai_pct', 'lcp_ms', 'inp_ms',
        'cls_x1000', 'bot_pct',
    );

    /**
     * The site's own $own_value against the cell's p25/p50/p75 for $metric.
     * Returns null when benchmarks are absent, $metric isn't one the hub
     * covers, or the entry is malformed — callers must treat null as
     * "render exactly today's UI, nothing extra".
     *
     * @return array{own:float,p25:float,p50:float,p75:float,n:int,position:string}|null
     */
    public static function comparison( $metric, $own_value ) {
        if ( ! in_array( $metric, self::METRICS, true ) ) {
            return null;
        }
        $metrics = self::feed_metrics();
        if ( null === $metrics || ! isset( $metrics[ $metric ] ) || ! is_array( $metrics[ $metric ] ) ) {
            return null;
        }
        $row = $metrics[ $metric ];
        if ( ! isset( $row['p25'], $row['p50'], $row['p75'] )
            || ! is_numeric( $row['p25'] ) || ! is_numeric( $row['p50'] ) || ! is_numeric( $row['p75'] )
            || ! is_numeric( $own_value ) ) {
            return null;
        }
        $p25 = (float) $row['p25'];
        $p75 = (float) $row['p75'];
        return array(
            'own'      => (float) $own_value,
            'p25'      => $p25,
            'p50'      => (float) $row['p50'],
            'p75'      => $p75,
            'n'        => self::cell_n(),
            'position' => self::position( (float) $own_value, $p25, $p75 ),
        );
    }

    /** Pure: below_p25 | mid | above_p75 — descriptive only, never a value judgement. */
    public static function position( $value, $p25, $p75 ) {
        if ( $value < $p25 ) {
            return 'below_p25';
        }
        if ( $value > $p75 ) {
            return 'above_p75';
        }
        return 'mid';
    }

    /**
     * Per-analyzer-check comparison: the industry's failure rate for
     * $check_key against the site's own. Returns null when absent/malformed.
     *
     * @return array{own_fail_pct:float,industry_fail_pct:float,n:int}|null
     */
    public static function analyzer_comparison( $check_key, $own_fail_pct ) {
        if ( ! is_string( $check_key ) || ! is_numeric( $own_fail_pct ) ) {
            return null;
        }
        $data = self::feed_analyzer();
        if ( null === $data || ! isset( $data[ $check_key ] ) || ! is_array( $data[ $check_key ] ) ) {
            return null;
        }
        $row = $data[ $check_key ];
        if ( ! isset( $row['fail_pct'] ) || ! is_numeric( $row['fail_pct'] ) ) {
            return null;
        }
        return array(
            'own_fail_pct'      => (float) $own_fail_pct,
            'industry_fail_pct' => (float) $row['fail_pct'],
            'n'                 => self::cell_n(),
        );
    }

    /** The peer-group sample size the current benchmark cell is drawn from, or 0. */
    public static function cell_n() {
        $feed = self::feed();
        if ( null === $feed || ! isset( $feed['cell'] ) || ! is_array( $feed['cell'] ) || ! isset( $feed['cell']['n'] ) || ! is_numeric( $feed['cell']['n'] ) ) {
            return 0;
        }
        return (int) $feed['cell']['n'];
    }

    /**
     * The honest stand-in for a benchmark comparison when `benchmarks` is
     * absent for this site's cell: turns the hub's `network` signal
     * ({sites, unlock_at}) into one factual, translatable sentence.
     *
     * Two distinct truths, told apart by the numbers:
     *  - sites < unlock_at:  comparisons are globally off — say how close.
     *  - sites >= unlock_at: the network has enough sites overall, but THIS
     *    site's own industry/region cell is still too thin — say that instead,
     *    never the global count (it would be misleading here).
     *
     * Returns null — render nothing — when the hub is too old to send
     * `network` at all, or when it sent something malformed (missing keys,
     * non-integers, negative counts). Never guesses, never a placeholder.
     *
     * @return string|null
     */
    public static function network_progress_message() {
        if ( ! class_exists( 'Seoforge_Intel_Client' ) ) {
            return null;
        }
        $network = Seoforge_Intel_Client::network();
        if ( ! is_array( $network )
            || ! isset( $network['sites'] ) || ! isset( $network['unlock_at'] )
            || ! is_int( $network['sites'] ) || ! is_int( $network['unlock_at'] )
            || $network['sites'] < 0 || $network['unlock_at'] <= 0 ) {
            return null;
        }
        $sites     = $network['sites'];
        $unlock_at = $network['unlock_at'];

        if ( $sites >= $unlock_at ) {
            return __( 'Not enough data for your industry or region yet to compare — this fills in as more similar sites join the SEO network.', 'brainwerk-seo-suite' );
        }

        return sprintf(
            /* translators: 1: number of sites required before comparisons unlock, 2: number of sites currently participating */
            __( 'Industry comparisons start once %1$d sites participate in the SEO network. Currently there are %2$d — the comparison stays switched off until then so no single site can be identified.', 'brainwerk-seo-suite' ),
            $unlock_at,
            $sites
        );
    }

    private static function feed() {
        if ( ! class_exists( 'Seoforge_Intel_Client' ) ) {
            return null;
        }
        $data = Seoforge_Intel_Client::benchmarks();
        return is_array( $data ) ? $data : null;
    }

    private static function feed_metrics() {
        $feed = self::feed();
        return ( $feed && isset( $feed['metrics'] ) && is_array( $feed['metrics'] ) ) ? $feed['metrics'] : null;
    }

    private static function feed_analyzer() {
        $feed = self::feed();
        return ( $feed && isset( $feed['analyzer'] ) && is_array( $feed['analyzer'] ) ) ? $feed['analyzer'] : null;
    }
}
