<?php
/**
 * Email notifications: daily + weekly HTML digests and ad-hoc alerts.
 *
 * The digests are graphical, email-client-safe HTML (table layout + inline
 * styles, no external CSS) rendered from Seoforge_Reports::aggregate(), so they
 * carry the same rich KPIs as the on-screen reports: period-over-period deltas,
 * traffic mix, top pages, human-vs-bot split, Core Web Vitals and geography —
 * "everything at a glance".
 *
 * @package Brainwerk SEO Suite
 */

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

class Seoforge_Notifications {

    /* Brand + status colours, reused inline throughout the email. */
    const C_NAVY  = '#1a2348';
    const C_NAVY2 = '#202b56';
    const C_GOLD  = '#b8860b';
    const C_GOOD  = '#16a34a';
    const C_BAD   = '#dc2626';
    const C_MUTED = '#6b7280';
    const C_LINE  = '#e5e7eb';
    const C_BG    = '#f3f4f6';

    public static function register( Seoforge_Loader $loader ) {
        $loader->add_action( 'seoforge_weekly_digest', __CLASS__, 'send_weekly_digest' );
        $loader->add_action( 'seoforge_daily_digest',  __CLASS__, 'send_daily_digest' );
        $loader->add_action( 'seoforge_notify', __CLASS__, 'send_email_notification', 10, 3 );
        $loader->add_action( 'admin_post_seoforge_test_digest', __CLASS__, 'handle_test_digest' );
        // Self-heal: ensure the digest crons exist even if the activation hook
        // never ran (e.g. a direct option-swap migration). Cheap no-op once set.
        $loader->add_action( 'init', __CLASS__, 'maybe_schedule' );
    }

    /** Schedule the daily + weekly digest events if missing (per site). */
    public static function maybe_schedule() {
        // UTC, consistent with Seoforge_Activator::schedule_events() and every
        // DB timestamp — see the comment there for why.
        if ( ! wp_next_scheduled( 'seoforge_weekly_digest' ) ) {
            wp_schedule_event( strtotime( 'next monday 07:00 UTC' ), 'weekly', 'seoforge_weekly_digest' );
        }
        if ( ! wp_next_scheduled( 'seoforge_daily_digest' ) ) {
            wp_schedule_event( strtotime( 'tomorrow 07:00 UTC' ), 'daily', 'seoforge_daily_digest' );
        }
    }

    /** Admin-triggered "send a test digest now" (Tools tab). */
    public static function handle_test_digest() {
        if ( ! current_user_can( 'manage_options' ) && ! current_user_can( 'manage_network_options' ) ) {
            wp_die( esc_html__( 'You do not have permission to do this.', 'brainwerk-seo-suite' ), 403 );
        }
        check_admin_referer( 'seoforge_test_digest' );
        $kind = ( isset( $_POST['kind'] ) && 'daily' === $_POST['kind'] ) ? 'daily' : 'weekly';
        $ok   = self::send_digest( $kind, true ); // force: ignore the per-kind enabled toggle.
        $arg  = $ok ? 'seoforge_testmail_ok' : 'seoforge_testmail_err';
        wp_safe_redirect( add_query_arg( $arg, $kind, admin_url( 'admin.php?page=brainwerk-seo-suite&tab=tools' ) ) );
        exit;
    }

    /**
     * Ad-hoc alert / notice e-mail (e.g. the Pro traffic-spike alert).
     *
     * Fired via the `seoforge_notify` action. $title + $body stay plain strings
     * (so the webhook fan-out keeps working); an optional $context array lets the
     * caller drive the graphical e-mail — severity colour, a clean headline, a
     * facts table and a CTA. Without context it still renders a branded card from
     * the title + body, so every notice now matches the digest CI.
     *
     * @param string $title   Short alert title (also the mail subject).
     * @param string $body    Plain-text detail line (fallback message).
     * @param array  $context Optional: severity|kicker|headline|message|facts|cta_url|cta_label.
     */
    public static function send_email_notification( $title, $body, $context = array() ) {
        if ( ! Seoforge_Settings::get( 'notify_email_enabled', false ) ) {
            return false;
        }
        $to = (string) Seoforge_Settings::get( 'notify_email_to', get_option( 'admin_email' ) );
        if ( ! is_email( $to ) ) {
            return false;
        }
        $context = is_array( $context ) ? $context : array();
        $html = self::render_notice_html( array(
            'severity'  => isset( $context['severity'] ) ? $context['severity'] : 'warning',
            'kicker'    => isset( $context['kicker'] ) ? $context['kicker'] : __( 'Alert', 'brainwerk-seo-suite' ),
            'headline'  => isset( $context['headline'] ) ? $context['headline'] : $title,
            'message'   => isset( $context['message'] ) ? $context['message'] : $body,
            'facts'     => isset( $context['facts'] ) ? (array) $context['facts'] : array(),
            'cta_url'   => isset( $context['cta_url'] ) ? $context['cta_url'] : admin_url( 'admin.php?page=brainwerk-seo-suite' ),
            'cta_label' => isset( $context['cta_label'] ) ? $context['cta_label'] : __( 'Open full dashboard', 'brainwerk-seo-suite' ),
        ) );
        return wp_mail( $to, '[Brainwerk SEO Suite] ' . $title, $html, array( 'Content-Type: text/html; charset=UTF-8' ) );
    }

    public static function send_weekly_digest() {
        if ( ! Seoforge_Settings::get( 'weekly_digest_enabled', true ) ) {
            return false;
        }
        return self::send_digest( 'weekly' );
    }

    public static function send_daily_digest() {
        if ( ! Seoforge_Settings::get( 'daily_digest_enabled', false ) ) {
            return false;
        }
        return self::send_digest( 'daily' );
    }

    /**
     * Build + send one digest. $kind = 'daily' | 'weekly'. Also used by the
     * "send test" button (which bypasses the per-kind enabled toggle).
     */
    public static function send_digest( $kind, $force = false ) {
        if ( ! $force && ! Seoforge_Settings::get( 'notify_email_enabled', false ) ) {
            return false;
        }
        $to = (string) Seoforge_Settings::get( 'notify_email_to', get_option( 'admin_email' ) );
        if ( ! is_email( $to ) ) {
            return false;
        }

        $today_start = strtotime( gmdate( 'Y-m-d 00:00:00' ) . ' UTC' );
        if ( 'daily' === $kind ) {
            $start = $today_start - DAY_IN_SECONDS; // yesterday 00:00
            $end   = $today_start - 1;              // yesterday 23:59:59
        } else {
            $start = $today_start - 7 * DAY_IN_SECONDS;
            $end   = $today_start - 1;
        }

        $data = Seoforge_Reports::aggregate( $start, $end );
        $site = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
        $html = self::render_digest_html( $data, $kind, $site, $start, $end );

        $label = 'daily' === $kind ? __( 'Daily report', 'brainwerk-seo-suite' ) : __( 'Weekly report', 'brainwerk-seo-suite' );
        $subject = sprintf( '[Brainwerk SEO Suite] %s · %s · %s', $label, $site, wp_date( 'd.m.Y', $start ) );

        return wp_mail( $to, $subject, $html, array( 'Content-Type: text/html; charset=UTF-8' ) );
    }

    /* ===================================================================
     * SHARED BRANDED SHELL  (reused by every plugin e-mail)
     * =================================================================== */

    /**
     * Status palette for notice e-mails. Returns accent / soft-bg / border /
     * icon for one of: info | success | warning | critical.
     */
    private static function severity_style( $severity ) {
        switch ( $severity ) {
            case 'success':
                return array( 'accent' => self::C_GOOD, 'bg' => '#f0fdf4', 'border' => '#bbf7d0', 'icon' => '&#10003;' );
            case 'warning':
                return array( 'accent' => '#b45309', 'bg' => '#fffbeb', 'border' => '#fde68a', 'icon' => '&#9650;' );
            case 'critical':
                return array( 'accent' => self::C_BAD, 'bg' => '#fef2f2', 'border' => '#fecaca', 'icon' => '&#9888;' );
            case 'info':
            default:
                return array( 'accent' => self::C_NAVY, 'bg' => '#eff4ff', 'border' => '#c7d7fe', 'icon' => '&#8505;' );
        }
    }

    /**
     * Email-client-safe branded shell — the SAME CI as the digests: navy-gradient
     * header with logo + gold kicker, white body, optional CTA button and the
     * "made in EU" footer line. $args['body_html'] is injected verbatim, so the
     * caller is responsible for escaping its dynamic parts.
     *
     * @param array $args kicker, site, subline, body_html, cta_url, cta_label, accent.
     * @return string Full HTML document.
     */
    public static function email_shell( array $args ) {
        $a = array_merge( array(
            'kicker'    => '',
            'site'      => wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ),
            'subline'   => '',
            'preheader' => '',
            'body_html' => '',
            'cta_url'   => admin_url( 'admin.php?page=brainwerk-seo-suite' ),
            'cta_label' => __( 'Open full dashboard', 'brainwerk-seo-suite' ),
            'accent'    => self::C_GOLD,
        ), $args );
        $logo = SEOFORGE_URL . 'assets/images/icon-256.png';
        $preheader = '' !== (string) $a['preheader'] ? $a['preheader'] : ( '' !== (string) $a['subline'] ? $a['subline'] : $a['kicker'] );
        ob_start();
        ?>
<!doctype html>
<html lang="<?php echo esc_attr( substr( get_locale(), 0, 2 ) ); ?>">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark"></head>
<body style="margin:0;padding:0;background:<?php echo self::C_BG; ?>;">
<div style="display:none;max-height:0;overflow:hidden;mso-hide:all;opacity:0;font-size:1px;line-height:1px;color:#f3f4f6;"><?php echo esc_html( $preheader ); ?></div>
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:<?php echo self::C_BG; ?>;">
<tr><td align="center" style="padding:20px 12px;">
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="width:600px;max-width:100%;background:#ffffff;border-radius:10px;overflow:hidden;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;">

    <!-- Header -->
    <tr><td style="background:<?php echo self::C_NAVY; ?>;background:linear-gradient(135deg,<?php echo self::C_NAVY; ?>,<?php echo self::C_NAVY2; ?>);padding:22px 28px;">
        <table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr>
            <td width="52" valign="middle"><img src="<?php echo esc_url( $logo ); ?>" width="44" height="44" alt="Brainwerk SEO Suite" style="display:block;border-radius:9px;"></td>
            <td valign="middle" style="padding-left:14px;">
                <?php if ( '' !== (string) $a['kicker'] ) : ?><div style="color:<?php echo esc_attr( $a['accent'] ); ?>;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:1.2px;"><?php echo esc_html( $a['kicker'] ); ?></div><?php endif; ?>
                <div style="color:#ffffff;font-size:20px;font-weight:700;"><?php echo esc_html( $a['site'] ); ?></div>
                <?php if ( '' !== (string) $a['subline'] ) : ?><div style="color:#cbd5e1;font-size:12px;padding-top:2px;"><?php echo esc_html( $a['subline'] ); ?></div><?php endif; ?>
            </td>
        </tr></table>
    </td></tr>

    <!-- Body -->
    <tr><td style="padding:20px 28px 4px;">
        <?php echo $a['body_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
    </td></tr>

    <!-- CTA -->
    <?php if ( ! empty( $a['cta_url'] ) && ! empty( $a['cta_label'] ) ) : ?>
    <tr><td style="padding:8px 28px 0;">
        <table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td align="center">
            <a href="<?php echo esc_url( $a['cta_url'] ); ?>" style="display:inline-block;background:<?php echo self::C_NAVY; ?>;color:#ffffff;text-decoration:none;font-size:14px;font-weight:600;padding:11px 22px;border-radius:6px;"><?php echo esc_html( $a['cta_label'] ); ?></a>
        </td></tr></table>
    </td></tr>
    <?php endif; ?>

    <!-- Footer -->
    <tr><td style="padding:18px 28px 26px;text-align:center;font-size:11px;color:<?php echo self::C_MUTED; ?>;">
        <?php
        /* translators: %s plugin version */
        printf( esc_html__( 'Brainwerk SEO Suite v%s · all data first-party, privacy-focused, made in EU.', 'brainwerk-seo-suite' ), esc_html( SEOFORGE_VERSION ) );
        ?>
        <div style="padding-top:6px;"><a href="<?php echo esc_url( admin_url( 'admin.php?page=brainwerk-seo-suite&tab=tools' ) ); ?>" style="color:<?php echo self::C_MUTED; ?>;text-decoration:underline;"><?php esc_html_e( 'Manage e-mail reports', 'brainwerk-seo-suite' ); ?></a></div>
    </td></tr>

</table>
</td></tr>
</table>
</body>
</html>
        <?php
        return ob_get_clean();
    }

    /**
     * Branded "notice" e-mail used by alerts, report cover notes and the test
     * mail. Renders a status-coloured hero band (icon + headline + message), an
     * optional facts table and optional extra HTML, then wraps it in email_shell().
     *
     * @param array $args severity, kicker, site, subline, headline, message,
     *                    intro, facts (label=>value), extra_html, cta_url, cta_label.
     * @return string Full HTML document.
     */
    public static function render_notice_html( array $args ) {
        $a = array_merge( array(
            'severity'  => 'info',
            'kicker'    => __( 'Notification', 'brainwerk-seo-suite' ),
            'site'      => wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ),
            'subline'   => '',
            'headline'  => '',
            'message'   => '',
            'intro'     => '',
            'facts'     => array(),
            'extra_html'=> '',
            'cta_url'   => admin_url( 'admin.php?page=brainwerk-seo-suite' ),
            'cta_label' => __( 'Open full dashboard', 'brainwerk-seo-suite' ),
        ), $args );
        $sev = self::severity_style( $a['severity'] );

        $body  = '';
        if ( '' !== (string) $a['intro'] ) {
            $body .= '<div style="font-size:14px;line-height:1.6;color:' . self::C_NAVY . ';padding-bottom:14px;">' . esc_html( $a['intro'] ) . '</div>';
        }

        // Hero band: circular status icon + headline + message.
        $body .= '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:' . $sev['bg'] . ';border:1px solid ' . $sev['border'] . ';border-radius:10px;"><tr>'
            . '<td width="66" valign="top" align="center" style="padding:16px 4px 16px 14px;">'
            . '<div style="width:40px;height:40px;line-height:40px;border-radius:50%;background:' . $sev['accent'] . ';color:#ffffff;font-size:18px;text-align:center;font-weight:700;">' . $sev['icon'] . '</div>'
            . '</td>'
            . '<td valign="middle" style="padding:14px 16px 14px 6px;">'
            . ( '' !== (string) $a['headline'] ? '<div style="font-size:17px;font-weight:700;color:' . self::C_NAVY . ';line-height:1.3;">' . esc_html( $a['headline'] ) . '</div>' : '' )
            . ( '' !== (string) $a['message'] ? '<div style="font-size:13px;line-height:1.55;color:' . self::C_NAVY . ';padding-top:5px;">' . esc_html( $a['message'] ) . '</div>' : '' )
            . '</td></tr></table>';

        // Optional facts table.
        if ( ! empty( $a['facts'] ) ) {
            $rows = '';
            foreach ( (array) $a['facts'] as $label => $value ) {
                $rows .= '<tr>'
                    . '<td style="padding:8px 0;border-bottom:1px solid ' . self::C_LINE . ';font-size:13px;color:' . self::C_MUTED . ';">' . esc_html( $label ) . '</td>'
                    . '<td style="padding:8px 0;border-bottom:1px solid ' . self::C_LINE . ';font-size:13px;font-weight:600;color:' . self::C_NAVY . ';text-align:right;">' . esc_html( $value ) . '</td>'
                    . '</tr>';
            }
            $body .= '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin-top:16px;">' . $rows . '</table>';
        }

        if ( '' !== (string) $a['extra_html'] ) {
            $body .= $a['extra_html'];
        }

        return self::email_shell( array(
            'kicker'    => $a['kicker'],
            'site'      => $a['site'],
            'subline'   => $a['subline'],
            'accent'    => $sev['accent'],
            'body_html' => $body,
            'cta_url'   => $a['cta_url'],
            'cta_label' => $a['cta_label'],
        ) );
    }

    /* ===================================================================
     * EMAIL-SAFE HTML RENDERER
     * =================================================================== */

    /** Coloured delta text for the email (down-is-good for bounce/404). */
    private static function edelta( $cur, $prev, $invert = false ) {
        if ( $prev <= 0 ) {
            return $cur > 0
                ? '<span style="color:' . self::C_MUTED . ';font-size:12px;">' . esc_html__( 'new', 'brainwerk-seo-suite' ) . '</span>'
                : '';
        }
        $pct = round( ( ( $cur - $prev ) / $prev ) * 100, 1 );
        if ( 0.0 === (float) $pct ) {
            return '<span style="color:' . self::C_MUTED . ';font-size:12px;">±0%</span>';
        }
        $up    = $pct > 0;
        $good  = $invert ? ! $up : $up;
        $color = $good ? self::C_GOOD : self::C_BAD;
        $arrow = $up ? '&#9650;' : '&#9660;';
        return '<span style="color:' . $color . ';font-size:12px;font-weight:600;">' . $arrow . ' ' . ( $up ? '+' : '' ) . esc_html( $pct ) . '%</span>';
    }

    /** Core Web Vitals rating chip → inline-styled span. $type: lcp|inp|cls. */
    private static function ecwv( $type, $value ) {
        $t = array( 'lcp' => array( 2500, 4000 ), 'inp' => array( 200, 500 ), 'cls' => array( 0.1, 0.25 ) );
        if ( ! isset( $t[ $type ] ) || $value <= 0 ) {
            return array( __( 'no data', 'brainwerk-seo-suite' ), self::C_MUTED, '#f3f4f6' );
        }
        list( $good, $ni ) = $t[ $type ];
        if ( $value <= $good ) {
            return array( __( 'Good', 'brainwerk-seo-suite' ), self::C_GOOD, '#dcfce7' );
        }
        if ( $value <= $ni ) {
            return array( __( 'Needs work', 'brainwerk-seo-suite' ), '#92600a', '#fef3c7' );
        }
        return array( __( 'Poor', 'brainwerk-seo-suite' ), self::C_BAD, '#fee2e2' );
    }

    /** One KPI cell (table-based, fixed 25% width). */
    private static function kpi_cell( $label, $value, $delta = '' ) {
        return '<td width="25%" valign="top" style="padding:10px 8px;border:1px solid ' . self::C_LINE . ';border-radius:6px;background:#ffffff;">'
            . '<div style="font-size:11px;color:' . self::C_MUTED . ';text-transform:uppercase;letter-spacing:.4px;">' . esc_html( $label ) . '</div>'
            . '<div style="font-size:20px;font-weight:700;color:' . self::C_NAVY . ';padding-top:2px;">' . esc_html( $value ) . '</div>'
            . ( $delta ? '<div style="padding-top:2px;">' . $delta . '</div>' : '' )
            . '</td>';
    }

    /** A labelled horizontal bar row (email-safe: nested width-% table). */
    private static function bar_row( $label, $pct, $color ) {
        $pct = max( 0, min( 100, (float) $pct ) );
        $w   = (int) round( $pct );
        return '<tr>'
            . '<td style="font-size:13px;color:' . self::C_NAVY . ';padding:4px 8px 4px 0;width:140px;">' . esc_html( $label ) . '</td>'
            . '<td style="padding:4px 0;">'
            . '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#eef0f4;border-radius:4px;"><tr>'
            . '<td width="' . $w . '%" style="background:' . $color . ';height:10px;line-height:10px;font-size:1px;border-radius:4px;">&nbsp;</td>'
            . '<td>&nbsp;</td></tr></table>'
            . '</td>'
            . '<td style="font-size:13px;color:' . self::C_NAVY . ';text-align:right;padding:4px 0 4px 8px;width:46px;">' . esc_html( $pct ) . '%</td>'
            . '</tr>';
    }

    /** Section heading. */
    private static function h2( $text ) {
        return '<tr><td style="padding:22px 0 8px;"><div style="font-size:15px;font-weight:700;color:' . self::C_NAVY . ';border-bottom:2px solid ' . self::C_GOLD . ';padding-bottom:5px;">' . esc_html( $text ) . '</div></td></tr>';
    }

    /**
     * Period health score (0–100) from the KPIs. Each signal is only counted
     * when it actually has data, then weighted + renormalised — so a server-mode
     * site with no engagement/CWV beacon is not unfairly marked "failing".
     * Returns array( int|null $score, string $label, string $color ).
     */
    private static function health( $k ) {
        $parts          = array(); // each: array( weight, value 0-100 )
        $no_engagement  = ( 0 === (int) $k['engaged_pct'] && 0 === (int) $k['duration_avg'] );
        $has_quality    = false;   // at least one engagement/bounce/CWV signal present

        if ( $k['engaged_pct'] > 0 ) {
            $parts[]     = array( 3, min( 100, (float) $k['engaged_pct'] ) );
            $has_quality = true;
        }
        // Bounce is unreliable when no beacon links multi-pageview sessions.
        if ( $k['sessions'] > 0 && ! ( $k['bounce_pct'] >= 99 && $no_engagement ) ) {
            $parts[]     = array( 2, max( 0, 100 - (float) $k['bounce_pct'] ) );
            $has_quality = true;
        }
        $vitals = array();
        foreach ( array( 'lcp' => $k['lcp_p75'], 'inp' => $k['inp_p75'], 'cls' => $k['cls_p75'] ) as $t => $v ) {
            if ( $v <= 0 ) { continue; }
            $r        = self::ecwv( $t, $v );
            $vitals[] = ( self::C_GOOD === $r[1] ) ? 100 : ( self::C_BAD === $r[1] ? 20 : 55 );
        }
        if ( $vitals ) {
            $parts[]     = array( 3, array_sum( $vitals ) / count( $vitals ) );
            $has_quality = true;
        }
        $parts[] = array( 1, $k['hits_404'] <= 0 ? 100 : max( 40, 100 - (int) $k['hits_404'] * 2 ) );
        if ( $k['views_prev'] > 0 ) {
            $g       = 50 + ( ( $k['views'] - $k['views_prev'] ) / $k['views_prev'] ) * 100;
            $parts[] = array( 1, max( 0, min( 100, $g ) ) );
        }

        if ( count( $parts ) < 2 ) {
            return array( null, __( 'Not enough data', 'brainwerk-seo-suite' ), self::C_MUTED );
        }
        $wsum = 0;
        $vsum = 0;
        foreach ( $parts as $p ) {
            $wsum += $p[0];
            $vsum += $p[0] * $p[1];
        }
        $score = (int) round( $vsum / $wsum );
        // Without an engagement/bounce/CWV signal the score rests only on traffic
        // + errors — informative but not a real quality verdict. Stay neutral.
        if ( ! $has_quality ) {
            return array( $score, __( 'Limited data', 'brainwerk-seo-suite' ), self::C_MUTED );
        }
        if ( $score >= 80 ) { return array( $score, __( 'Healthy', 'brainwerk-seo-suite' ), self::C_GOOD ); }
        if ( $score >= 60 ) { return array( $score, __( 'Fair', 'brainwerk-seo-suite' ), '#92600a' ); }
        return array( $score, __( 'Needs attention', 'brainwerk-seo-suite' ), self::C_BAD );
    }

    /** One plain-language headline summarising the period (already escaped on output). */
    private static function insight( $data ) {
        $k    = $data['kpis'];
        $bits = array();

        if ( $k['views_prev'] > 0 ) {
            $pct = (int) round( ( ( $k['views'] - $k['views_prev'] ) / $k['views_prev'] ) * 100 );
            if ( $pct >= 5 ) {
                /* translators: %d = percent increase */
                $bits[] = sprintf( __( 'Traffic is up %d%% on the previous period.', 'brainwerk-seo-suite' ), $pct );
            } elseif ( $pct <= -5 ) {
                /* translators: %d = percent decrease */
                $bits[] = sprintf( __( 'Traffic is down %d%% on the previous period.', 'brainwerk-seo-suite' ), abs( $pct ) );
            } else {
                $bits[] = __( 'Traffic held steady versus the previous period.', 'brainwerk-seo-suite' );
            }
        } elseif ( $k['views'] > 0 ) {
            $bits[] = __( 'First traffic recorded for this period.', 'brainwerk-seo-suite' );
        }

        $labels = array(
            'search'   => __( 'search engines', 'brainwerk-seo-suite' ),
            'direct'   => __( 'direct visits', 'brainwerk-seo-suite' ),
            'referral' => __( 'referrals', 'brainwerk-seo-suite' ),
            'social'   => __( 'social media', 'brainwerk-seo-suite' ),
            'campaign' => __( 'campaigns', 'brainwerk-seo-suite' ),
        );
        $best  = '';
        $bestv = 0;
        foreach ( $labels as $key => $lbl ) {
            $v = isset( $data['src_pct'][ $key ] ) ? (float) $data['src_pct'][ $key ] : 0;
            if ( $v > $bestv ) {
                $bestv = $v;
                $best  = $lbl;
            }
        }
        if ( $best && $bestv > 0 ) {
            /* translators: 1: percent, 2: traffic source label e.g. "search engines" */
            $bits[] = sprintf( __( '%1$d%% of visits came from %2$s.', 'brainwerk-seo-suite' ), (int) round( $bestv ), $best );
        }

        $tp = isset( $data['top_pages'][0] ) ? $data['top_pages'][0] : null;
        if ( $tp && ! empty( $tp['url_path'] ) ) {
            /* translators: %s = page path */
            $bits[] = sprintf( __( 'Most-viewed page: %s', 'brainwerk-seo-suite' ), $tp['url_path'] );
        }

        return implode( ' ', $bits );
    }

    /** Email-safe vertical bar chart of daily views (table cells, no SVG/images). */
    private static function trend_chart( $trend ) {
        $rows = array();
        foreach ( (array) $trend as $t ) {
            $rows[] = array( 'day' => $t['day'], 'views' => (int) $t['views'] );
        }
        if ( count( $rows ) < 2 ) {
            return '';
        }
        $max = 1;
        foreach ( $rows as $r ) {
            $max = max( $max, $r['views'] );
        }
        $w     = (int) floor( 100 / count( $rows ) );
        $cells = '';
        foreach ( $rows as $r ) {
            $h  = max( 3, (int) round( ( $r['views'] / $max ) * 70 ) );
            $dl = wp_date( 'D', strtotime( $r['day'] . ' 12:00:00 UTC' ) );
            $cells .= '<td valign="bottom" align="center" width="' . $w . '%" style="padding:0 2px;">'
                . '<div style="font-size:10px;color:' . self::C_NAVY . ';font-weight:600;padding-bottom:3px;">' . esc_html( number_format_i18n( $r['views'] ) ) . '</div>'
                . '<div style="height:' . $h . 'px;background:' . self::C_GOLD . ';border-radius:3px 3px 0 0;font-size:1px;line-height:1px;">&nbsp;</div>'
                . '<div style="font-size:10px;color:' . self::C_MUTED . ';padding-top:4px;">' . esc_html( $dl ) . '</div>'
                . '</td>';
        }
        return '<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr>' . $cells . '</tr></table>';
    }

    /** A horizontal stacked bar from [ ['pct'=>, 'color'=>], ... ]. */
    private static function stacked_bar( $segments ) {
        $cells = '';
        foreach ( $segments as $s ) {
            $sw = max( 0, min( 100, (int) round( $s['pct'] ) ) );
            if ( $sw <= 0 ) { continue; }
            $cells .= '<td width="' . $sw . '%" style="background:' . $s['color'] . ';height:14px;line-height:14px;font-size:1px;">&nbsp;</td>';
        }
        if ( '' === $cells ) {
            $cells = '<td style="background:#eef0f4;height:14px;line-height:14px;font-size:1px;">&nbsp;</td>';
        }
        return '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border-radius:7px;overflow:hidden;"><tr>' . $cells . '</tr></table>';
    }

    /** Legend chip: coloured square + label + value. */
    private static function legend_dot( $color, $label, $pct ) {
        return '<span style="white-space:nowrap;font-size:12px;color:' . self::C_NAVY . ';padding-right:14px;">'
            . '<span style="display:inline-block;width:9px;height:9px;border-radius:2px;background:' . $color . ';">&nbsp;</span> '
            . esc_html( $label ) . ' <strong>' . esc_html( $pct ) . '%</strong></span>';
    }

    /** A half-width titled card wrapping arbitrary (already-built) inner HTML. */
    private static function mini_card( $title, $inner_html ) {
        return '<td width="50%" valign="top" style="padding:0 6px;">'
            . '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border:1px solid ' . self::C_LINE . ';border-radius:8px;"><tr><td style="padding:12px 14px;">'
            . '<div style="font-size:12px;font-weight:700;color:' . self::C_NAVY . ';padding-bottom:8px;">' . esc_html( $title ) . '</div>'
            . $inner_html
            . '</td></tr></table></td>';
    }

    private static function render_digest_html( $data, $kind, $site, $start, $end ) {
        $k       = $data['kpis'];
        $period  = 'daily' === $kind ? wp_date( 'd.m.Y', $start ) : wp_date( 'd.m.Y', $start ) . ' – ' . wp_date( 'd.m.Y', $end );
        $kindlbl = 'daily' === $kind ? __( 'Daily report', 'brainwerk-seo-suite' ) : __( 'Weekly report', 'brainwerk-seo-suite' );
        $logo    = SEOFORGE_URL . 'assets/images/icon-256.png';
        $dash    = admin_url( 'admin.php?page=brainwerk-seo-suite' );

        // Hero ingredients.
        list( $score, $score_lbl, $score_col ) = self::health( $k );
        $insight    = self::insight( $data );
        $trend_html = self::trend_chart( $data['trend'] );

        // Device + audience splits.
        $dev      = isset( $data['dev_pct'] ) ? $data['dev_pct'] : array();
        $dev_m    = isset( $dev['mobile'] ) ? (float) $dev['mobile'] : 0;
        $dev_d    = isset( $dev['desktop'] ) ? (float) $dev['desktop'] : 0;
        $dev_t    = isset( $dev['tablet'] ) ? (float) $dev['tablet'] : 0;
        $new_pct  = (float) $k['new_pct'];
        $ret_pct  = max( 0, 100 - $new_pct );

        // Source labels for the bars.
        $src_labels = array(
            'search'   => __( 'Search engines', 'brainwerk-seo-suite' ),
            'direct'   => __( 'Direct', 'brainwerk-seo-suite' ),
            'referral' => __( 'Referrals', 'brainwerk-seo-suite' ),
            'social'   => __( 'Social media', 'brainwerk-seo-suite' ),
            'campaign' => __( 'Campaigns', 'brainwerk-seo-suite' ),
        );

        ob_start();
        ?>
<!doctype html>
<html lang="<?php echo esc_attr( substr( get_locale(), 0, 2 ) ); ?>">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<meta name="supported-color-schemes" content="light dark"></head>
<body style="margin:0;padding:0;background:<?php echo self::C_BG; ?>;">
<div style="display:none;max-height:0;overflow:hidden;mso-hide:all;opacity:0;font-size:1px;line-height:1px;color:#f3f4f6;"><?php echo esc_html( $insight ); ?></div>
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:<?php echo self::C_BG; ?>;">
<tr><td align="center" style="padding:20px 12px;">
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="width:600px;max-width:100%;background:#ffffff;border-radius:10px;overflow:hidden;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;">

    <!-- Header -->
    <tr><td style="background:<?php echo self::C_NAVY; ?>;background:linear-gradient(135deg,<?php echo self::C_NAVY; ?>,<?php echo self::C_NAVY2; ?>);padding:22px 28px;">
        <table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr>
            <td width="52" valign="middle"><img src="<?php echo esc_url( $logo ); ?>" width="44" height="44" alt="Brainwerk SEO Suite" style="display:block;border-radius:9px;"></td>
            <td valign="middle" style="padding-left:14px;">
                <div style="color:<?php echo self::C_GOLD; ?>;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:1.2px;"><?php echo esc_html( $kindlbl ); ?></div>
                <div style="color:#ffffff;font-size:20px;font-weight:700;"><?php echo esc_html( $site ); ?></div>
                <div style="color:#cbd5e1;font-size:12px;padding-top:2px;"><?php echo esc_html( $period ); ?></div>
            </td>
        </tr></table>
    </td></tr>

    <!-- Hero: health score + plain-language insight -->
    <tr><td style="padding:16px 28px 0;">
        <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f7f8fb;border:1px solid <?php echo self::C_LINE; ?>;border-radius:10px;"><tr>
            <td width="118" valign="middle" align="center" style="padding:16px 6px;">
                <div style="font-size:11px;color:<?php echo self::C_MUTED; ?>;text-transform:uppercase;letter-spacing:.5px;"><?php esc_html_e( 'Health', 'brainwerk-seo-suite' ); ?></div>
                <div style="font-size:38px;font-weight:800;line-height:1.05;color:<?php echo $score_col; ?>;"><?php echo null === $score ? '&mdash;' : esc_html( $score ); ?></div>
                <div style="font-size:11px;font-weight:700;color:<?php echo $score_col; ?>;"><?php echo esc_html( $score_lbl ); ?></div>
            </td>
            <td valign="middle" style="padding:16px 16px 16px 6px;border-left:1px solid <?php echo self::C_LINE; ?>;">
                <div style="font-size:13px;line-height:1.55;color:<?php echo self::C_NAVY; ?>;"><?php echo esc_html( $insight ); ?></div>
            </td>
        </tr></table>
    </td></tr>

    <tr><td style="padding:8px 28px 28px;">
        <table role="presentation" width="100%" cellpadding="0" cellspacing="0">

            <!-- KPI grid: two rows of four -->
            <?php echo self::h2( __( 'At a glance', 'brainwerk-seo-suite' ) ); ?>
            <tr><td>
                <table role="presentation" width="100%" cellpadding="0" cellspacing="6"><tr>
                    <?php
                    echo self::kpi_cell( __( 'Pageviews', 'brainwerk-seo-suite' ), number_format_i18n( $k['views'] ), self::edelta( $k['views'], $k['views_prev'] ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                    echo self::kpi_cell( __( 'Unique visitors', 'brainwerk-seo-suite' ), number_format_i18n( $k['uniq'] ), self::edelta( $k['uniq'], $k['uniq_prev'] ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                    echo self::kpi_cell( __( 'Sessions', 'brainwerk-seo-suite' ), number_format_i18n( $k['sessions'] ), self::edelta( $k['sessions'], $k['sessions_prev'] ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                    echo self::kpi_cell( __( 'Conversions', 'brainwerk-seo-suite' ), number_format_i18n( $k['conversions'] ), self::edelta( $k['conversions'], $k['conversions_prev'] ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                    ?>
                </tr></table>
                <table role="presentation" width="100%" cellpadding="0" cellspacing="6"><tr>
                    <?php
                    echo self::kpi_cell( __( 'Bounce rate', 'brainwerk-seo-suite' ), $k['bounce_pct'] . '%', self::edelta( $k['bounce_pct'], $k['bounce_pct_prev'], true ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                    echo self::kpi_cell( __( 'Avg. session', 'brainwerk-seo-suite' ), gmdate( 'i:s', max( 0, (int) $k['duration_avg'] ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                    echo self::kpi_cell( __( 'New visitors', 'brainwerk-seo-suite' ), $k['new_pct'] . '%' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                    echo self::kpi_cell( __( 'Bot share', 'brainwerk-seo-suite' ), $k['bot_pct'] . '%' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                    ?>
                </tr></table>
            </td></tr>

            <!-- Pageviews trend (weekly: 7 daily bars; hidden when <2 days) -->
            <?php if ( '' !== $trend_html ) : ?>
                <?php echo self::h2( __( 'Pageviews trend', 'brainwerk-seo-suite' ) ); ?>
                <tr><td style="padding-top:6px;"><?php echo $trend_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></td></tr>
            <?php endif; ?>

            <!-- Traffic sources -->
            <?php echo self::h2( __( 'Traffic sources', 'brainwerk-seo-suite' ) ); ?>
            <tr><td><table role="presentation" width="100%" cellpadding="0" cellspacing="0">
                <?php
                $any_src = false;
                foreach ( $src_labels as $key => $lbl ) {
                    $pct = isset( $data['src_pct'][ $key ] ) ? (float) $data['src_pct'][ $key ] : 0;
                    if ( $pct <= 0 ) { continue; }
                    $any_src = true;
                    echo self::bar_row( $lbl, $pct, self::C_GOLD ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                }
                if ( ! $any_src ) {
                    echo '<tr><td style="font-size:13px;color:' . self::C_MUTED . ';">' . esc_html__( 'No data yet.', 'brainwerk-seo-suite' ) . '</td></tr>';
                }
                ?>
            </table></td></tr>

            <!-- Devices + audience split -->
            <?php echo self::h2( __( 'Devices & audience', 'brainwerk-seo-suite' ) ); ?>
            <tr><td><table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr>
                <?php
                $dev_bar = self::stacked_bar( array(
                    array( 'pct' => $dev_m, 'color' => self::C_GOLD ),
                    array( 'pct' => $dev_d, 'color' => self::C_NAVY ),
                    array( 'pct' => $dev_t, 'color' => '#9aa3b2' ),
                ) );
                $dev_legend = '<div style="padding-top:8px;line-height:1.9;">'
                    . self::legend_dot( self::C_GOLD, __( 'Mobile', 'brainwerk-seo-suite' ), $dev_m )
                    . self::legend_dot( self::C_NAVY, __( 'Desktop', 'brainwerk-seo-suite' ), $dev_d )
                    . ( $dev_t > 0 ? self::legend_dot( '#9aa3b2', __( 'Tablet', 'brainwerk-seo-suite' ), $dev_t ) : '' )
                    . '</div>';
                echo self::mini_card( __( 'Devices', 'brainwerk-seo-suite' ), $dev_bar . $dev_legend ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

                $aud_bar = self::stacked_bar( array(
                    array( 'pct' => $new_pct, 'color' => self::C_GOLD ),
                    array( 'pct' => $ret_pct, 'color' => self::C_NAVY ),
                ) );
                $aud_legend = '<div style="padding-top:8px;line-height:1.9;">'
                    . self::legend_dot( self::C_GOLD, __( 'New', 'brainwerk-seo-suite' ), $new_pct )
                    . self::legend_dot( self::C_NAVY, __( 'Returning', 'brainwerk-seo-suite' ), $ret_pct )
                    . '</div>';
                echo self::mini_card( __( 'New vs returning', 'brainwerk-seo-suite' ), $aud_bar . $aud_legend ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                ?>
            </tr></table></td></tr>

            <!-- Top pages -->
            <?php echo self::h2( __( 'Top pages', 'brainwerk-seo-suite' ) ); ?>
            <tr><td><table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="font-size:13px;">
                <?php
                $tp = array_slice( (array) $data['top_pages'], 0, 5 );
                if ( empty( $tp ) ) {
                    echo '<tr><td style="color:' . self::C_MUTED . ';">' . esc_html__( 'No data yet.', 'brainwerk-seo-suite' ) . '</td></tr>';
                } else {
                    foreach ( $tp as $p ) {
                        echo '<tr><td style="padding:5px 0;border-bottom:1px solid ' . self::C_LINE . ';color:' . self::C_NAVY . ';">' . esc_html( $p['url_path'] ) . '</td>'
                            . '<td style="padding:5px 0;border-bottom:1px solid ' . self::C_LINE . ';text-align:right;color:' . self::C_NAVY . ';font-weight:600;">' . esc_html( number_format_i18n( $p['hits'] ) ) . '</td></tr>';
                    }
                }
                ?>
            </table></td></tr>

            <!-- Core Web Vitals -->
            <?php echo self::h2( __( 'Core Web Vitals (p75)', 'brainwerk-seo-suite' ) ); ?>
            <tr><td><table role="presentation" width="100%" cellpadding="0" cellspacing="6"><tr>
                <?php
                $cwv = array(
                    array( 'LCP', $k['lcp_p75'] ? number_format_i18n( $k['lcp_p75'] ) . ' ms' : '—', self::ecwv( 'lcp', $k['lcp_p75'] ) ),
                    array( 'INP', $k['inp_p75'] ? number_format_i18n( $k['inp_p75'] ) . ' ms' : '—', self::ecwv( 'inp', $k['inp_p75'] ) ),
                    array( 'CLS', $k['cls_p75'] ? (string) $k['cls_p75'] : '—', self::ecwv( 'cls', $k['cls_p75'] ) ),
                );
                foreach ( $cwv as $c ) {
                    echo '<td width="33%" valign="top" style="padding:10px 8px;border:1px solid ' . self::C_LINE . ';border-radius:6px;">'
                        . '<div style="font-size:11px;color:' . self::C_MUTED . ';">' . esc_html( $c[0] ) . '</div>'
                        . '<div style="font-size:18px;font-weight:700;color:' . self::C_NAVY . ';padding:2px 0;">' . esc_html( $c[1] ) . '</div>'
                        . '<span style="font-size:11px;font-weight:700;color:' . $c[2][1] . ';background:' . $c[2][2] . ';padding:2px 8px;border-radius:999px;">' . esc_html( $c[2][0] ) . '</span>'
                        . '</td>';
                }
                ?>
            </tr></table></td></tr>

            <!-- Footer -->
            <tr><td style="padding-top:26px;">
                <table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr><td align="center">
                    <a href="<?php echo esc_url( $dash ); ?>" style="display:inline-block;background:<?php echo self::C_NAVY; ?>;color:#ffffff;text-decoration:none;font-size:14px;font-weight:600;padding:11px 22px;border-radius:6px;"><?php esc_html_e( 'Open full dashboard', 'brainwerk-seo-suite' ); ?></a>
                </td></tr></table>
            </td></tr>
            <tr><td style="padding-top:18px;text-align:center;font-size:11px;color:<?php echo self::C_MUTED; ?>;">
                <?php
                /* translators: %s plugin version */
                printf( esc_html__( 'Brainwerk SEO Suite v%s · all data first-party, privacy-focused, made in EU.', 'brainwerk-seo-suite' ), esc_html( SEOFORGE_VERSION ) );
                ?>
                <div style="padding-top:6px;"><a href="<?php echo esc_url( admin_url( 'admin.php?page=brainwerk-seo-suite&tab=tools' ) ); ?>" style="color:<?php echo self::C_MUTED; ?>;text-decoration:underline;"><?php esc_html_e( 'Manage e-mail reports', 'brainwerk-seo-suite' ); ?></a></div>
            </td></tr>

        </table>
    </td></tr>
</table>
</td></tr>
</table>
</body>
</html>
        <?php
        return ob_get_clean();
    }
}
