<?php
/**
 * Gestionnaire centralise des emails transactionnels via Brevo.
 *
 * Sprint 11 : IDs de templates lus dans wp_options (avi_brevo_tpl_*),
 * types symboliques TYPE_* + legacy TEMPLATE_* / entier 41.
 */
namespace Avision;

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

class Avision_Email_Manager {

    /** ID du template transactionnel Brevo « Welcome B2C » (cockpit + identifiants). */
    const TEMPLATE_WELCOME_B2C    = 41;
    const TEMPLATE_OUT_OF_INFUSION = 'OUT_OF_INFUSION';
    const TEMPLATE_GUARDIAN_PURCHASE = 'GUARDIAN_PURCHASE';
    const TEMPLATE_REPORTS = 'REPORTS';

    const TYPE_PAYMENT_FAILED     = 'PAYMENT_FAILED';
    const TYPE_STRIPE_ACTIVATE    = 'STRIPE_ACTIVATE';
    const TYPE_STRIPE_CANCEL      = 'STRIPE_CANCEL';
    const TYPE_GUARDIAN_UNLOCK    = 'GUARDIAN_UNLOCK';
    const TYPE_LTD_WELCOME        = 'LTD_WELCOME';
    const TYPE_AGENCY_WELCOME     = 'AGENCY_WELCOME';
    const TYPE_LTD_TRIAL_START    = 'LTD_TRIAL_START';
    const TYPE_LEVEL_3            = 'LEVEL_3';
    const TYPE_LEVEL_4_GUARDIAN   = 'LEVEL_4_GUARDIAN';
    const TYPE_LEVEL_12           = 'LEVEL_12';
    const TYPE_LEVEL_15           = 'LEVEL_15';
    const TYPE_LEVEL_18           = 'LEVEL_18';
    const TYPE_LEVEL_20           = 'LEVEL_20';
    const TYPE_LEVEL_21           = 'LEVEL_21';
    const TYPE_WEEKLY_REPORT      = 'WEEKLY_REPORT';
    const TYPE_MONTHLY_REPORT     = 'MONTHLY_REPORT';
    const TYPE_REACTIVATION       = 'REACTIVATION';
    const TYPE_INFUSION_END       = 'INFUSION_END';
    const TYPE_INFUSION_ACTIVE    = 'INFUSION_ACTIVE';
    const TYPE_INFUSION_MEDIUM    = 'INFUSION_MEDIUM';
    const TYPE_INFUSION_SHORT     = 'INFUSION_SHORT';

    const TYPE_GUARDIAN_VALIDATION = 'GUARDIAN_VALIDATION';
    const TYPE_GUARDIAN_ALERT_NLP  = 'GUARDIAN_ALERT_NLP';
    const TYPE_GUARDIAN_REPLY_AUTO = 'GUARDIAN_REPLY_AUTO';
    const TYPE_CRISIS_URGENT        = 'CRISIS_URGENT';
    const TYPE_CRISIS_NEUTRALIZED   = 'CRISIS_NEUTRALIZED';
    const TYPE_ACCESS_SUSPENDED     = 'ACCESS_SUSPENDED';
    const TYPE_REENGAGE_B2B         = 'REENGAGE_B2B';
    const TYPE_NONPROFIT_APPROVED   = 'NONPROFIT_APPROVED';
    const TYPE_DONATION_MONTHLY     = 'DONATION_MONTHLY';
    const TYPE_NONPROFIT_WARNING    = 'NONPROFIT_WARNING';
    const TYPE_UNSUBSCRIBE_CONFIRM  = 'UNSUBSCRIBE_CONFIRM';
    const TYPE_WELCOME_COCKPIT      = 'WELCOME_COCKPIT';
    const TYPE_GALLERY_WARNING      = 'gallery_warning';

    /**
     * Point d'entree principal d'envoi transactionnel.
     *
     * @param int          $user_id        ID utilisateur SaaS (avi_saas_users) ou WordPress selon contexte ; 0 si `recipient_email` fourni.
     * @param int|string   $template_type  Entier legacy 41, chaînes TEMPLATE_* ou TYPE_*.
     * @param array        $data           Données template ; `recipient_email` force le destinataire.
     * @return bool
     */
    public static function send_avi_email($user_id, $template_type, $data = []) {
        $user_id = (int) $user_id;
        if ($template_type === '' || $template_type === null) {
            return false;
        }

        $data = is_array($data) ? $data : [];
        $email_lang_for_resolve = isset($data['email_lang']) ? strtolower(trim((string) $data['email_lang'])) : '';
        if (!in_array($email_lang_for_resolve, ['fr', 'en', 'es'], true)) {
            $email_lang_for_resolve = null;
        }

        $recipient = ['email' => '', 'name' => ''];
        if ($user_id > 0) {
            $recipient = self::get_user_recipient($user_id);
            if (empty($recipient['email']) && function_exists('get_userdata')) {
                $wp_u = get_userdata($user_id);
                if ($wp_u instanceof \WP_User && is_email((string) $wp_u->user_email)) {
                    $recipient['email'] = sanitize_email((string) $wp_u->user_email);
                    $recipient['name'] = (string) $wp_u->display_name;
                }
            }
        }
        if (!empty($data['recipient_email']) && is_email($data['recipient_email'])) {
            $recipient['email'] = sanitize_email((string) $data['recipient_email']);
        }
        if (empty($recipient['email'])) {
            error_log('[AVI EMAIL] No recipient (user_id=' . $user_id . ')');
            return false;
        }

        $api_key = '';
        if (defined('AVISION_BREVO_API_KEY') && is_string(AVISION_BREVO_API_KEY) && trim(AVISION_BREVO_API_KEY) !== '') {
            $api_key = trim(AVISION_BREVO_API_KEY);
        }
        if ($api_key === '') {
            $api_key = self::get_brevo_api_key();
        }
        if ($api_key === '') {
            error_log('[AVI EMAIL] Missing Brevo API key');
            return false;
        }

        $recipient_email = isset($recipient['email']) ? (string) $recipient['email'] : '';

        $resolved_tid = self::resolve_numeric_brevo_template_id($template_type, $email_lang_for_resolve);

        if (self::is_legacy_welcome_b2c_numeric($template_type)) {
            error_log('[BREVO] Trigger manuel détecté pour Welcome B2C (magic link)');
            $activation_url = isset($data['activation_url']) ? trim((string) $data['activation_url']) : '';
            if ($activation_url === '') {
                error_log('[BREVO] Envoi Welcome B2C annulé : activation_url vide pour l\'email : "' . $recipient_email . '"');
                return false;
            }
        }

        $payload = self::build_template_payload($template_type, $recipient, $data, $resolved_tid);
        if (empty($payload)) {
            error_log('[AVI EMAIL] Unknown template_type=' . $template_type);
            return false;
        }

        if (self::is_legacy_welcome_b2c_numeric($template_type)) {
            error_log('[BREVO] Tentative envoi Welcome B2C (magic link présent) | email="' . $recipient_email . '"');
        }

        $response = wp_remote_post('https://api.brevo.com/v3/smtp/email', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
                'api-key' => $api_key,
            ],
            'timeout' => 20,
            'body' => wp_json_encode($payload),
        ]);

        if (is_wp_error($response)) {
            $ctx = self::is_legacy_welcome_b2c_numeric($template_type) ? ' [WELCOME_B2C / magic link]' : '';
            $brevo_transport_detail = $response->get_error_message();
            foreach ($response->get_error_codes() as $err_code) {
                $msgs = $response->get_error_messages($err_code);
                $brevo_transport_detail .= ' | [' . $err_code . '] ' . implode(' ; ', $msgs);
                $err_data = $response->get_error_data($err_code);
                if (!empty($err_data)) {
                    $brevo_transport_detail .= ' | data(' . $err_code . ')=' . (is_scalar($err_data) ? (string) $err_data : wp_json_encode($err_data));
                }
            }
            error_log('[BREVO] Échec envoi (transport WordPress) — détail complet : ' . $brevo_transport_detail);
            error_log('[AVI EMAIL] Brevo wp_remote_post error' . $ctx . ': ' . $response->get_error_message());
            return false;
        }

        $status = (int) wp_remote_retrieve_response_code($response);
        $body_raw = wp_remote_retrieve_body($response);
        $ctx_welcome = self::is_legacy_welcome_b2c_numeric($template_type) ? ' [WELCOME_B2C / magic link]' : '';

        if ($status < 200 || $status >= 300) {
            error_log('[BREVO] Échec API — HTTP ' . $status . ' — réponse Brevo complète : ' . $body_raw);
            error_log(
                '[AVI EMAIL] Brevo API erreur' . $ctx_welcome . ': HTTP ' . $status
                . ' template=' . $template_type
            );
            return false;
        }

        $body_log = (string) $body_raw;
        if (strlen($body_log) > 8192) {
            $body_log = substr($body_log, 0, 8192) . '…[truncated]';
        }
        error_log(
            '[AVI EMAIL] Brevo raw response user_id=' . $user_id
            . ' template=' . $template_type
            . ' HTTP=' . $status
            . ' body=' . $body_log
        );

        error_log(
            '[AVI EMAIL] Brevo API OK' . $ctx_welcome . ': HTTP ' . $status
            . ' template=' . $template_type
        );

        return true;
    }

    /**
     * Bienvenue B2C historique : seul l’entier 41 est reconnu (non modifié côté appelants).
     *
     * @param mixed $template_type
     * @return bool
     */
    private static function is_legacy_welcome_b2c_numeric($template_type) {
        return is_numeric($template_type) && (int) $template_type === (int) self::TEMPLATE_WELCOME_B2C;
    }

    /**
     * Résout l'ID numérique Brevo (templateId) depuis wp_options ou legacy.
     *
     * @param mixed $template_type
     * @return int 0 si inconnu / option vide.
     */
    private static function resolve_numeric_brevo_template_id($template_type, $email_lang = null) {
        if (is_numeric($template_type) && (int) $template_type === (int) self::TEMPLATE_WELCOME_B2C) {
            $tpl_map_b2c = ['fr' => 129, 'es' => 130, 'en' => 41];
            if ($email_lang !== null && isset($tpl_map_b2c[$email_lang])) {
                return $tpl_map_b2c[$email_lang];
            }
            $opt = get_option('avi_brevo_tpl_welcome_b2c', (string) (int) self::TEMPLATE_WELCOME_B2C);
            $id = (int) $opt;
            return $id > 0 ? $id : (int) self::TEMPLATE_WELCOME_B2C;
        }

        if (!is_string($template_type)) {
            return 0;
        }

        $type = trim($template_type);
        if ($type === self::TEMPLATE_OUT_OF_INFUSION) {
            $type = self::TYPE_INFUSION_END;
        } elseif ($type === self::TEMPLATE_GUARDIAN_PURCHASE) {
            $type = self::TYPE_LEVEL_4_GUARDIAN;
        } elseif ($type === self::TEMPLATE_REPORTS) {
            $type = self::TYPE_MONTHLY_REPORT;
        }

        return self::get_brevo_template_id($type, $email_lang);
    }

    /**
     * Mapping type symbolique → option wp_options (valeur = ID template Brevo).
     *
     * @param string $type
     * @return int
     */
    private static function get_brevo_template_id($type, $lang = null) {
        $map = [
            self::TYPE_PAYMENT_FAILED   => 'avi_brevo_tpl_payment_failed',
            self::TYPE_STRIPE_ACTIVATE  => 'avi_brevo_tpl_stripe_activate',
            self::TYPE_STRIPE_CANCEL     => 'avi_brevo_tpl_stripe_cancel',
            self::TYPE_GUARDIAN_UNLOCK  => 'avi_brevo_tpl_guardian_unlock',
            self::TYPE_LTD_WELCOME       => 'avi_brevo_tpl_ltd_welcome',
            self::TYPE_AGENCY_WELCOME    => 'avi_brevo_tpl_ltd_agency_welcome',
            self::TYPE_LTD_TRIAL_START   => 'avi_brevo_tpl_ltd_trial_start',
            self::TYPE_LEVEL_3           => 'avi_brevo_tpl_level_3_photo1',
            self::TYPE_LEVEL_4_GUARDIAN  => 'avi_brevo_tpl_level_4_guardian',
            self::TYPE_LEVEL_12          => 'avi_brevo_tpl_level_12_clean',
            self::TYPE_LEVEL_15          => 'avi_brevo_tpl_level_15_reviews',
            self::TYPE_LEVEL_18          => 'avi_brevo_tpl_level_18_posts',
            self::TYPE_LEVEL_20          => 'avi_brevo_tpl_level_20_photo2',
            self::TYPE_LEVEL_21          => 'avi_brevo_tpl_level_21_elite',
            self::TYPE_WEEKLY_REPORT     => 'avi_brevo_tpl_weekly_report',
            self::TYPE_MONTHLY_REPORT    => 'avi_brevo_tpl_monthly_report',
            self::TYPE_REACTIVATION      => 'avi_brevo_tpl_reactivation',
            self::TYPE_INFUSION_END      => 'avi_brevo_tpl_infusion_end',
            self::TYPE_INFUSION_ACTIVE   => 'avi_brevo_tpl_infusion_active',
            self::TYPE_INFUSION_MEDIUM   => 'avi_brevo_tpl_infusion_medium',
            self::TYPE_INFUSION_SHORT    => 'avi_brevo_tpl_infusion_short',
            self::TYPE_GUARDIAN_VALIDATION => 'avi_brevo_tpl_guardian_validation',
            self::TYPE_GUARDIAN_ALERT_NLP  => 'avi_brevo_tpl_guardian_alert_nlp',
            self::TYPE_GUARDIAN_REPLY_AUTO => 'avi_brevo_tpl_guardian_reply_auto',
            self::TYPE_CRISIS_URGENT       => 'avi_brevo_tpl_crisis_urgent',
            self::TYPE_CRISIS_NEUTRALIZED  => 'avi_brevo_tpl_crisis_neutralized',
            self::TYPE_ACCESS_SUSPENDED    => 'avi_brevo_tpl_access_suspended',
            self::TYPE_REENGAGE_B2B        => 'avi_brevo_tpl_reengage_b2b',
            self::TYPE_NONPROFIT_APPROVED  => 'avi_brevo_tpl_nonprofit_approved',
            self::TYPE_DONATION_MONTHLY    => 'avi_brevo_tpl_donation_monthly',
            self::TYPE_NONPROFIT_WARNING   => 'avi_brevo_tpl_nonprofit_warning',
            self::TYPE_UNSUBSCRIBE_CONFIRM  => 'avi_brevo_tpl_unsubscribe_confirm',
            self::TYPE_WELCOME_COCKPIT      => 'avi_brevo_tpl_welcome_cockpit',
            self::TYPE_GALLERY_WARNING      => 'avi_brevo_tpl_gallery_warning',
        ];
        if ($type === self::TYPE_WELCOME_COCKPIT && $lang !== null) {
            $tpl_map_cockpit = ['fr' => 135, 'es' => 136, 'en' => 58];
            if (isset($tpl_map_cockpit[$lang])) {
                return $tpl_map_cockpit[$lang];
            }
        }
        $option = isset($map[$type]) ? $map[$type] : '';
        if ($option === '') {
            return 0;
        }
        $raw = get_option($option, '');
        $id = is_scalar($raw) ? (int) trim((string) $raw) : 0;
        return $id > 0 ? $id : 0;
    }

    /**
     * Envoie un message transactionnel brut via Brevo (POST /v3/smtp/email sans templateId).
     *
     * @param array $args {
     *     @type string $recipient_email Destinataire (ex. contact@avision.org).
     *     @type string $subject       Objet.
     *     @type string $text_body     Corps texte (obligatoire si html_body vide).
     *     @type string $html_body     Corps HTML (optionnel ; sinon dérivé de text_body).
     *     @type string $reply_to_email E-mail visiteur (Reply-To).
     *     @type string $reply_to_name  Nom visiteur (optionnel).
     * }
     * @return bool True si Brevo répond 2xx.
     */
    public static function send_public_contact_inquiry(array $args) {
        $args = wp_parse_args(
            $args,
            array(
                'recipient_email' => 'contact@avision.org',
                'subject' => '',
                'text_body' => '',
                'html_body' => '',
                'reply_to_email' => '',
                'reply_to_name' => '',
            )
        );

        $to = sanitize_email((string) $args['recipient_email']);
        if ($to === '' || !is_email($to)) {
            error_log('[AVI EMAIL] Contact form: invalid recipient');
            return false;
        }

        $subject = sanitize_text_field((string) $args['subject']);
        if ($subject === '') {
            $subject = '[AVISION] Contact form';
        }

        $text_body = (string) $args['text_body'];
        $html_body = (string) $args['html_body'];
        if ($text_body === '' && $html_body === '') {
            error_log('[AVI EMAIL] Contact form: empty body');
            return false;
        }

        if ($html_body === '') {
            $html_body = '<div style="font-family:system-ui,-apple-system,sans-serif;font-size:15px;line-height:1.5;color:#111;">'
                . '<pre style="white-space:pre-wrap;margin:0;">' . esc_html($text_body) . '</pre></div>';
        }

        if ($text_body === '') {
            $text_body = wp_strip_all_tags($html_body);
        }

        $api_key = '';
        if (defined('AVISION_BREVO_API_KEY') && is_string(AVISION_BREVO_API_KEY) && trim(AVISION_BREVO_API_KEY) !== '') {
            $api_key = trim(AVISION_BREVO_API_KEY);
        }
        if ($api_key === '') {
            $api_key = self::get_brevo_api_key();
        }
        if ($api_key === '') {
            error_log('[AVI EMAIL] Contact form: missing Brevo API key');
            return false;
        }

        $payload = array(
            'sender' => array(
                'name' => self::sender_name(),
                'email' => self::sender_email(),
            ),
            'to' => array(array('email' => $to, 'name' => '')),
            'subject' => $subject,
            'textContent' => $text_body,
            'htmlContent' => $html_body,
        );

        $reply = sanitize_email((string) $args['reply_to_email']);
        if ($reply !== '' && is_email($reply)) {
            $payload['replyTo'] = array(
                'email' => $reply,
                'name' => sanitize_text_field((string) $args['reply_to_name']),
            );
        }

        $response = wp_remote_post('https://api.brevo.com/v3/smtp/email', array(
            'headers' => array(
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
                'api-key' => $api_key,
            ),
            'timeout' => 20,
            'body' => wp_json_encode($payload),
        ));

        if (is_wp_error($response)) {
            error_log('[AVI EMAIL] Contact form Brevo transport: ' . $response->get_error_message());
            return false;
        }

        $status = (int) wp_remote_retrieve_response_code($response);
        $body_raw = wp_remote_retrieve_body($response);
        if ($status < 200 || $status >= 300) {
            error_log('[AVI EMAIL] Contact form Brevo HTTP ' . $status . ' body=' . substr((string) $body_raw, 0, 2048));
            return false;
        }

        error_log('[AVI EMAIL] Contact form Brevo OK HTTP=' . $status);
        return true;
    }

    /**
     * @return string
     */
    private static function get_brevo_api_key() {
        if (defined('AVISION_BREVO_API_KEY') && is_string(AVISION_BREVO_API_KEY) && trim(AVISION_BREVO_API_KEY) !== '') {
            return trim(AVISION_BREVO_API_KEY);
        }

        $option_key = get_option('avision_brevo_api_key', '');
        if (is_string($option_key) && trim($option_key) !== '') {
            return trim($option_key);
        }

        return '';
    }

    /**
     * @param int $user_id ID avi_saas_users.
     * @return array{email:string,name:string}
     */
    private static function get_user_recipient($user_id) {
        global $wpdb;

        $table = $wpdb->prefix . 'avi_saas_users';
        $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        if ($exists !== $table) {
            return ['email' => '', 'name' => ''];
        }

        $row = $wpdb->get_row(
            $wpdb->prepare("SELECT email FROM `{$table}` WHERE id = %d LIMIT 1", (int) $user_id),
            ARRAY_A
        );
        $email = isset($row['email']) ? sanitize_email((string) $row['email']) : '';

        return [
            'email' => $email,
            'name' => '',
        ];
    }

    /**
     * @param mixed $template_type
     * @param array $recipient
     * @param array $data
     * @param int   $resolved_tid
     * @return array
     */
    private static function build_template_payload($template_type, $recipient, $data, $resolved_tid) {
        $to = [[
            'email' => $recipient['email'],
            'name' => $recipient['name'],
        ]];

        $sender_email = self::sender_email();
        $sender_name = self::sender_name();

        if (self::is_legacy_welcome_b2c_numeric($template_type)) {
            $business_name = isset($data['business_name']) ? (string) $data['business_name'] : '';
            $dashboard_url = isset($data['dashboard_url']) ? (string) $data['dashboard_url'] : 'https://avision.org/premium-dashboard/';
            $login_url = isset($data['login_url']) ? (string) $data['login_url'] : 'https://avision.org/connexion/';
            $activation_url = isset($data['activation_url']) ? trim((string) $data['activation_url']) : '';
            $user_email = isset($recipient['email']) ? (string) $recipient['email'] : '';
            $email_lang = isset($data['email_lang']) ? strtolower(trim((string) $data['email_lang'])) : 'fr';
            if (!in_array($email_lang, ['fr', 'en', 'es'], true)) {
                $email_lang = 'fr';
            }

            return [
                'sender' => [
                    'name' => $sender_name,
                    'email' => $sender_email,
                ],
                'to' => $to,
                'templateId' => $resolved_tid > 0 ? $resolved_tid : (int) self::TEMPLATE_WELCOME_B2C,
                'params' => [
                    'BUSINESS_NAME' => $business_name,
                    'DASHBOARD_URL' => $dashboard_url,
                    'LOGIN_URL' => $login_url,
                    'ACTIVATION_URL' => $activation_url,
                    'MAGIC_LINK' => $activation_url,
                    'PASSWORD' => '',
                    'USER_EMAIL' => $user_email,
                    'EMAIL' => $user_email,
                    'EMAIL_LANG' => strtoupper($email_lang),
                ],
            ];
        }

        if ($resolved_tid > 0) {
            $params = self::build_brevo_params_for_transactional($template_type, $recipient, $data);
            return [
                'sender' => [
                    'name' => $sender_name,
                    'email' => $sender_email,
                ],
                'to' => $to,
                'templateId' => $resolved_tid,
                'params' => $params,
            ];
        }

        $subject_map = [
            self::TEMPLATE_OUT_OF_INFUSION => 'Tes donnees J0 sont pretes',
            self::TEMPLATE_GUARDIAN_PURCHASE => 'Mode Gardien active',
            self::TEMPLATE_REPORTS => 'Ton rapport AVI est disponible',
        ];

        if (!isset($subject_map[$template_type])) {
            return [];
        }

        $params = [
            'template_type' => $template_type,
            'business_name' => isset($data['business_name']) ? (string) $data['business_name'] : '',
            'dashboard_url' => isset($data['dashboard_url']) ? (string) $data['dashboard_url'] : home_url('/premium-dashboard'),
            'report_url' => isset($data['report_url']) ? (string) $data['report_url'] : '',
            'period' => isset($data['period']) ? (string) $data['period'] : '',
            'score' => isset($data['score']) ? (string) $data['score'] : '',
        ];

        $html = self::build_html_fallback($template_type, $params);

        return [
            'sender' => [
                'name' => $sender_name,
                'email' => $sender_email,
            ],
            'to' => $to,
            'subject' => $subject_map[$template_type],
            'htmlContent' => $html,
            'params' => $params,
        ];
    }

    /**
     * @param mixed $template_type
     * @param array $recipient
     * @param array $data
     * @return array<string,string>
     */
    private static function build_brevo_params_for_transactional($template_type, $recipient, $data) {
        $email = isset($recipient['email']) ? (string) $recipient['email'] : '';
        $name = isset($data['user_name']) ? (string) $data['user_name'] : (isset($recipient['name']) ? (string) $recipient['name'] : '');
        $dashboard = isset($data['dashboard_url']) ? (string) $data['dashboard_url'] : home_url('/premium-dashboard/');
        $login = isset($data['login_url']) ? (string) $data['login_url'] : home_url('/connexion/');
        $portal = isset($data['portal_url']) ? (string) $data['portal_url'] : '';
        $plan = isset($data['plan_label']) ? (string) $data['plan_label'] : '';
        $level = isset($data['level']) ? (string) (int) $data['level'] : '';
        $attempt = isset($data['attempt_nb']) ? (string) (int) $data['attempt_nb'] : '';
        $suspended = isset($data['suspended']) ? (string) $data['suspended'] : '';
        $pass = isset($data['user_pass']) ? (string) $data['user_pass'] : '';
        $business = isset($data['business_name']) ? (string) $data['business_name'] : '';
        $score = isset($data['score']) ? (string) $data['score'] : '';
        $report = isset($data['report_url']) ? (string) $data['report_url'] : '';
        $period = isset($data['period']) ? (string) $data['period'] : '';
        $listing_count = isset($data['listing_count']) ? (string) (int) $data['listing_count'] : '';
        $price_per = isset($data['price_per_listing']) ? (string) (int) $data['price_per_listing'] : '';
        $monthly_total = isset($data['monthly_total']) ? (string) (int) $data['monthly_total'] : '';
        $wholesale_tier = isset($data['wholesale_tier']) ? (string) (int) $data['wholesale_tier'] : '';
        $account_type_label = isset($data['account_type']) ? (string) $data['account_type'] : '';

        $out = [
            'USER_NAME' => $name,
            'USER_EMAIL' => $email,
            'EMAIL' => $email,
            'DASHBOARD_URL' => $dashboard,
            'LOGIN_URL' => $login,
            'PORTAL_URL' => $portal,
            'PLAN_LABEL' => $plan,
            'LEVEL' => $level,
            'ATTEMPT_NB' => $attempt,
            'SUSPENDED' => $suspended,
            'BUSINESS_NAME' => $business,
            'SCORE' => $score,
            'REPORT_URL' => $report,
            'PERIOD' => $period,
            'LISTING_COUNT' => $listing_count,
            'PRICE_PER_LISTING' => $price_per,
            'MONTHLY_TOTAL' => $monthly_total,
            'WHOLESALE_TIER' => $wholesale_tier,
            'ACCOUNT_TYPE' => $account_type_label,
        ];
        if ($pass !== '') {
            $out['USER_PASS'] = $pass;
        }
        $extra_from_data = [
            'reviewer_name' => 'REVIEWER_NAME',
            'stars_display' => 'STARS_DISPLAY',
            'review_text' => 'REVIEW_TEXT',
            'suggested_reply' => 'SUGGESTED_REPLY',
            'pending_hours' => 'PENDING_HOURS',
            'alert_type' => 'ALERT_TYPE',
            'detection_reason' => 'DETECTION_REASON',
            'reply_text' => 'REPLY_TEXT',
            'inactive_days' => 'INACTIVE_DAYS',
            'critical_count' => 'CRITICAL_COUNT',
            'unchecked_profiles' => 'UNCHECKED_PROFILES',
            'unsubscribe_url' => 'UNSUBSCRIBE_URL',
            'contact_name' => 'CONTACT_NAME',
            'org_name' => 'ORG_NAME',
            'referral_code' => 'REFERRAL_CODE',
            'payout_method' => 'PAYOUT_METHOD',
            'active_members' => 'ACTIVE_MEMBERS',
            'deadline' => 'DEADLINE',
            'resubscribe_url' => 'RESUBSCRIBE_URL',
            'reviews_week' => 'REVIEWS_WEEK',
            'posts_week' => 'POSTS_WEEK',
            'actions_total' => 'ACTIONS_TOTAL',
            'sprint_unlocked' => 'SPRINT_UNLOCKED',
            'sprint_label' => 'SPRINT_LABEL',
            'score_current' => 'SCORE_CURRENT',
            'score_day1' => 'SCORE_DAY1',
            'gallery_url' => 'GALLERY_URL',
        ];
        foreach ($extra_from_data as $data_key => $param_key) {
            if (isset($data[$data_key])) {
                $out[$param_key] = is_scalar($data[$data_key]) ? (string) $data[$data_key] : '';
            }
        }
        foreach ($data as $dk => $dv) {
            if (!is_string($dk) || $dk === '' || isset($extra_from_data[$dk])) {
                continue;
            }
            if (preg_match('/^[A-Z][A-Z0-9_]*$/', $dk)) {
                $out[$dk] = is_scalar($dv) ? (string) $dv : '';
            }
        }
        foreach ($out as $k => $v) {
            $out[$k] = is_string($v) ? $v : '';
        }
        // ANTI-RÉGRESSION #100 [Sprint 32] — params Brevo UPPER + lower.
        foreach ($data as $dk => $dv) {
            if (is_string($dk) && preg_match('/^[a-z][a-z0-9_]*$/', $dk)) {
                $out[$dk] = is_scalar($dv) ? (string) $dv : '';
            }
        }
        foreach ($out as $k => $v) {
            $lk = strtolower($k);
            if ($lk !== $k && !isset($out[$lk])) {
                $out[$lk] = $v;
            }
        }
        return $out;
    }

    /**
     * @param mixed $template_type
     * @param array $params
     * @return string
     */
    private static function build_html_fallback($template_type, $params) {
        $business_name = esc_html($params['business_name'] ?: 'ton entreprise');
        $dashboard_url = esc_url($params['dashboard_url']);
        $report_url = esc_url($params['report_url']);
        $login_url = !empty($params['login_url']) ? esc_url($params['login_url']) : esc_url(wp_login_url());

        switch ($template_type) {
            case self::TEMPLATE_WELCOME_B2C:
                $uid = esc_html($params['user_email'] ?? '');
                $act = isset($params['activation_url']) ? esc_url((string) $params['activation_url']) : '';
                $html = '<p>Bienvenue ' . $business_name . ',</p>';
                $html .= '<p>Votre Cockpit AVI est prêt.</p>';
                $html .= '<p>Voici les accès à votre Cockpit AVI&nbsp;:</p>';
                if ($uid !== '') {
                    $html .= '<p><strong>Identifiant&nbsp;:</strong> ' . $uid . '</p>';
                }
                if ($act !== '') {
                    $html .= '<p><strong><a href="' . $act . '">Définir mon mot de passe et activer mon accès</a></strong></p>';
                }
                $html .= '<p><a href="https://avision.org/connexion/">Accéder à la page de connexion</a></p>';
                $html .= '<p><a href="' . $dashboard_url . '">Ouvrir mon Dashboard Premium</a></p>';
                $html .= '<p>Votre accompagnement AVI démarre maintenant.</p>';
                return $html;
            case self::TEMPLATE_OUT_OF_INFUSION:
                return '<p>Le premier scan complet (J0) est termine.</p><p>Tes donnees sont pretes dans ton dashboard AVI.</p><p><a href="' . $dashboard_url . '">Voir mes donnees</a></p>';
            case self::TEMPLATE_GUARDIAN_PURCHASE:
                return '<p>Le Mode Gardien est actif.</p><p>La protection automatique de ta fiche est maintenant en route.</p><p><a href="' . $dashboard_url . '">Ouvrir mon espace</a></p>';
            case self::TEMPLATE_REPORTS:
                return '<p>Ton rapport AVI est disponible.</p><p><a href="' . ($report_url ?: $dashboard_url) . '">Consulter le rapport</a></p>';
            default:
                return '';
        }
    }

    /**
     * @return string
     */
    private static function sender_email() {
        $sender = get_option('avision_brevo_sender_email', '');
        if (is_string($sender) && trim($sender) !== '') {
            return trim($sender);
        }
        return 'no-reply@avision.org'; // ANTI-RÉGRESSION #99 [Sprint 32]
    }

    /**
     * @return string
     */
    private static function sender_name() {
        $name = get_option('avision_brevo_sender_name', '');
        if (is_string($name) && trim($name) !== '') {
            return trim($name);
        }
        return 'AVI';
    }
}
