<?php
/**
 * CRON Command Center — tâches planifiées AVI / Avision + inventaire emails.
 *
 * @package Avision_Dashboard
 */

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

/**
 * Sous-menu admin sous « AVI Control Center » : lecture de _get_cron_array(), exécution manuelle sécurisée.
 */
class Avision_Cron_Dashboard {

	const PAGE_SLUG   = 'avi-automations-cron';
	const PARENT_SLUG = 'avi-avi-control-center';

	/** Slugs de regroupement visuel (tableau CRON). */
	const GROUP_DATAFORSEO = 'dataforseo';
	const GROUP_GBP        = 'gbp';
	const GROUP_SAAS       = 'saas';
	const GROUP_OTHER      = 'other';

	/**
	 * @var self|null
	 */
	private static $instance = null;

	/**
	 * Bootstrap.
	 *
	 * @return void
	 */
	public static function init() {
		if ( self::$instance !== null ) {
			return;
		}
		self::$instance = new self();
		add_action( 'admin_menu', array( self::$instance, 'register_menu' ), 25 );
		add_action( 'admin_init', array( self::$instance, 'handle_run_now' ), 5 );
		add_action( 'admin_enqueue_scripts', array( self::$instance, 'enqueue_assets' ) );
		add_action( 'admin_head', array( self::$instance, 'print_submenu_clock_icon_css' ) );
	}

	/**
	 * @param string $hook Nom du hook cron.
	 * @return bool
	 */
	public static function is_avi_cron_hook( $hook ) {
		$hook = (string) $hook;
		if ( $hook === '' ) {
			return false;
		}
		$lower = strtolower( $hook );
		return ( 0 === strpos( $lower, 'avi_' ) || 0 === strpos( $lower, 'avision_' ) );
	}

	/**
	 * @param string $hook Hook.
	 * @return string HTML badges (echo-safe after esc_html on text).
	 */
	public static function target_badges_html( $hook ) {
		$h = strtolower( (string) $hook );
		$badges = array();

		// Orchestrateur Mode Gardien : cible GBP, pas le moteur DataForSEO (le hook contient « scan » sinon).
		if ( 'avision_hourly_scan_orchestrator' === $h ) {
			return '<span class="avi-cc-badge avi-cc-badge--gbp">' . esc_html__( 'Google Business Profile', 'avision-dashboard' ) . '</span>';
		}

		if ( false !== strpos( $h, 'google' ) || false !== strpos( $h, 'gbp' ) || false !== strpos( $h, 'gmb' ) ) {
			$badges[] = '<span class="avi-cc-badge avi-cc-badge--google">' . esc_html__( 'Google', 'avision-dashboard' ) . '</span>';
		}
		if ( false !== strpos( $h, 'scan' ) || false !== strpos( $h, 'queue' ) || false !== strpos( $h, 'keyword' ) || false !== strpos( $h, 'dataforseo' ) || false !== strpos( $h, 'rank' ) ) {
			$badges[] = '<span class="avi-cc-badge avi-cc-badge--dfs">DataForSEO</span>';
		}
		if ( false !== strpos( $h, 'report' ) ) {
			$badges[] = '<span class="avi-cc-badge avi-cc-badge--report">' . esc_html__( 'Rapports', 'avision-dashboard' ) . '</span>';
		}

		if ( empty( $badges ) ) {
			$badges[] = '<span class="avi-cc-badge avi-cc-badge--core">' . esc_html__( 'Interne', 'avision-dashboard' ) . '</span>';
		}

		return implode( ' ', $badges );
	}

	/**
	 * Sous-titre explicatif affiché sous le nom du hook (hooks canoniques + repli court).
	 *
	 * @param string $hook Hook cron.
	 * @return string Texte non vide pour l’affichage (échapper à l’affichage).
	 */
	public static function get_hook_table_subtitle( $hook ) {
		$h = strtolower( (string) $hook );
		$known = array(
			'avision_hourly_scan_orchestrator' => __( 'B2C & B2B : Le Général des Armées. Vérifie le NAP, les photos, lit les alertes concurrents, et écrit dans le journal des interventions. Fait appel aux APIs de base.', 'avision-dashboard' ),
			'avision_keyword_scan_queue'       => __( 'B2C & B2B : Vérifie les classements (Radar) de chaque mot-clé via DataForSEO.', 'avision-dashboard' ),
			'avision_weekly_report'            => __( 'B2C & B2B : Compile l’historique et envoie les rapports PDF en marque blanche.', 'avision-dashboard' ),
			'avision_monthly_report'           => __( 'B2C & B2B : Compile l’historique et envoie les rapports PDF en marque blanche.', 'avision-dashboard' ),
			'avi_cron_sync_keywords'           => __( 'Technique / héritage : synchronisation ou file mots-clés selon version du plugin.', 'avision-dashboard' ),
			'avi_cron_hourly_scan'             => __( 'Scan horaire (alias ou héritage) : peut coexister avec l’orchestrateur ; voir logs/cron-task.log.', 'avision-dashboard' ),
			'avi_cron_cleanup_orphans'         => __( 'Interne : nettoyage de données orphelines en base.', 'avision-dashboard' ),
			'avi_cron_check_google_sync'       => __( 'B2C & B2B : vérification cohérence token / sync Google Business Profile.', 'avision-dashboard' ),
		);
		if ( isset( $known[ $h ] ) ) {
			return $known[ $h ];
		}
		return __( 'Hook AVI : consulter le code du plugin ou docs/DOCUMENTATION_CRON_AVI.md.', 'avision-dashboard' );
	}

	/**
	 * Regroupement visuel des hooks CRON par « API » / domaine fonctionnel.
	 *
	 * @return array<string, array{title:string, description:string}>
	 */
	public static function get_cron_group_definitions() {
		return array(
			self::GROUP_DATAFORSEO => array(
				'title'       => '🚀 ' . __( 'DataForSEO (Scan Engine)', 'avision-dashboard' ),
				'description' => __( 'Mots-clés, Radar.', 'avision-dashboard' ),
			),
			self::GROUP_GBP        => array(
				'title'       => '📍 ' . __( 'Google Business Profile', 'avision-dashboard' ),
				'description' => __( 'Mode Gardien, Orchestrateur, Sync.', 'avision-dashboard' ),
			),
			self::GROUP_SAAS       => array(
				'title'       => '🏢 ' . __( 'SaaS & e-mails', 'avision-dashboard' ),
				'description' => __( 'Rapports hebdomadaires / mensuels, alertes.', 'avision-dashboard' ),
			),
			self::GROUP_OTHER      => array(
				'title'       => __( 'Autres tâches AVI', 'avision-dashboard' ),
				'description' => __( 'Hooks avi_* / avision_* non classés ci-dessus (extensions ou évolutions).', 'avision-dashboard' ),
			),
		);
	}

	/**
	 * Catégorie d’affichage pour un hook (aligné sur l’inventaire du plugin).
	 *
	 * @param string $hook Hook cron.
	 * @return string self::GROUP_* .
	 */
	public static function categorize_cron_hook( $hook ) {
		$h = strtolower( (string) $hook );
		// Référence : class-avision-cron.php + cron-task-manager.php (seuls wp_schedule_event du plugin).
		if ( 'avision_keyword_scan_queue' === $h ) {
			return self::GROUP_DATAFORSEO;
		}
		if ( 'avision_hourly_scan_orchestrator' === $h ) {
			return self::GROUP_GBP;
		}
		if ( in_array( $h, array( 'avision_weekly_report', 'avision_monthly_report' ), true ) ) {
			return self::GROUP_SAAS;
		}
		// Heuristique (hooks tiers / futurs).
		if ( false !== strpos( $h, 'report' ) || false !== strpos( $h, 'email' ) || false !== strpos( $h, 'alert' ) || false !== strpos( $h, 'notification' ) ) {
			return self::GROUP_SAAS;
		}
		if ( false !== strpos( $h, 'keyword' ) || false !== strpos( $h, 'dataforseo' ) || false !== strpos( $h, 'rank' ) ) {
			return self::GROUP_DATAFORSEO;
		}
		if ( false !== strpos( $h, 'google' ) || false !== strpos( $h, 'gbp' ) || false !== strpos( $h, 'gmb' ) || false !== strpos( $h, 'guardian' ) || false !== strpos( $h, 'orchestrat' ) || false !== strpos( $h, 'hourly_scan' ) ) {
			return self::GROUP_GBP;
		}
		return self::GROUP_OTHER;
	}

	/**
	 * @param array<int, array<string,mixed>> $rows collect_avi_cron_rows().
	 * @return array<string, array<int, array<string,mixed>>>
	 */
	public static function group_cron_rows_by_api( array $rows ) {
		$grouped = array();
		foreach ( array_keys( self::get_cron_group_definitions() ) as $gid ) {
			$grouped[ $gid ] = array();
		}
		foreach ( $rows as $r ) {
			$g = self::categorize_cron_hook( isset( $r['hook'] ) ? $r['hook'] : '' );
			if ( ! isset( $grouped[ $g ] ) ) {
				$g = self::GROUP_OTHER;
			}
			$grouped[ $g ][] = $r;
		}
		return $grouped;
	}

	/**
	 * @param string|false $schedule Clé schedule WP.
	 * @param int          $interval   Intervalle en secondes.
	 * @param array        $schedules  wp_get_schedules().
	 * @return string
	 */
	private static function schedule_label( $schedule, $interval, array $schedules ) {
		if ( false === $schedule || '' === (string) $schedule ) {
			return __( 'Une fois', 'avision-dashboard' );
		}
		$key = (string) $schedule;
		$fr  = array(
			'hourly'       => __( 'Horaire', 'avision-dashboard' ),
			'daily'        => __( 'Quotidien', 'avision-dashboard' ),
			'weekly'       => __( 'Hebdomadaire', 'avision-dashboard' ),
			'monthly'      => __( 'Mensuel', 'avision-dashboard' ),
			'every_minute' => __( 'Chaque minute', 'avision-dashboard' ),
		);
		if ( isset( $fr[ $key ] ) ) {
			return $fr[ $key ];
		}
		if ( isset( $schedules[ $key ]['display'] ) ) {
			return (string) $schedules[ $key ]['display'];
		}
		if ( $interval > 0 ) {
			/* translators: %s: interval in seconds */
			return sprintf( __( 'Répété (%s s)', 'avision-dashboard' ), (string) (int) $interval );
		}
		return esc_html( $key );
	}

	/**
	 * @return array<int, array{hook:string,timestamp:int,schedule:mixed,schedule_label:string,args:array,sig:string}>
	 */
	public static function collect_avi_cron_rows() {
		if ( ! function_exists( '_get_cron_array' ) ) {
			return array();
		}
		$crons = _get_cron_array();
		if ( empty( $crons ) || ! is_array( $crons ) ) {
			return array();
		}
		$schedules = wp_get_schedules();
		$rows      = array();

		foreach ( $crons as $timestamp => $hooks ) {
			if ( ! is_array( $hooks ) ) {
				continue;
			}
			foreach ( $hooks as $hook => $dings ) {
				if ( ! self::is_avi_cron_hook( $hook ) ) {
					continue;
				}
				if ( ! is_array( $dings ) ) {
					continue;
				}
				foreach ( $dings as $sig => $data ) {
					if ( ! is_array( $data ) ) {
						continue;
					}
					$schedule = array_key_exists( 'schedule', $data ) ? $data['schedule'] : false;
					$args     = isset( $data['args'] ) && is_array( $data['args'] ) ? $data['args'] : array();
					$interval = isset( $data['interval'] ) ? (int) $data['interval'] : 0;
					$rows[]   = array(
						'hook'            => (string) $hook,
						'timestamp'       => (int) $timestamp,
						'schedule'        => $schedule,
						'schedule_label'  => self::schedule_label( $schedule, $interval, $schedules ),
						'args'            => $args,
						'sig'             => (string) $sig,
					);
				}
			}
		}

		usort(
			$rows,
			static function ( $a, $b ) {
				if ( $a['timestamp'] === $b['timestamp'] ) {
					return strcmp( $a['hook'], $b['hook'] );
				}
				return $a['timestamp'] - $b['timestamp'];
			}
		);

		return $rows;
	}

	/**
	 * @param array $args Arguments cron (rappelés via do_action_ref_array).
	 * @return array
	 */
	private static function sanitize_cron_args_list( array $args ) {
		$out = array();
		foreach ( $args as $v ) {
			if ( is_scalar( $v ) || null === $v ) {
				$out[] = $v;
			}
		}
		return $out;
	}

	/**
	 * Sous-page sous AVI Control Center.
	 *
	 * @return void
	 */
	public function register_menu() {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		add_submenu_page(
			self::PARENT_SLUG,
			__( 'Automatisations (CRON)', 'avision-dashboard' ),
			__( 'Automatisations (CRON)', 'avision-dashboard' ),
			'manage_options',
			self::PAGE_SLUG,
			array( $this, 'render_page' ),
			6
		);
	}

	/**
	 * POST : exécuter un hook maintenant (do_action_ref_array).
	 *
	 * @return void
	 */
	public function handle_run_now() {
		if ( ! is_admin() || ! isset( $_POST['avi_cron_dashboard_action'] ) ) {
			return;
		}
		if ( ! isset( $_POST['avi_cron_dashboard_page'] ) || (string) wp_unslash( $_POST['avi_cron_dashboard_page'] ) !== self::PAGE_SLUG ) {
			return;
		}
		if ( (string) wp_unslash( $_POST['avi_cron_dashboard_action'] ) !== 'run_now' ) {
			return;
		}
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Accès refusé.', 'avision-dashboard' ), 403 );
		}
		check_admin_referer( 'avi_cron_dashboard_run_now' );

		$hook = isset( $_POST['hook'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['hook'] ) ) : '';
		if ( ! self::is_avi_cron_hook( $hook ) ) {
			$this->redirect_with_arg( 'avi_cron_err', 'hook' );
		}

		$raw = isset( $_POST['args_json'] ) ? wp_unslash( (string) $_POST['args_json'] ) : '[]';
		$args = json_decode( $raw, true );
		if ( ! is_array( $args ) ) {
			$args = array();
		}
		$args = self::sanitize_cron_args_list( $args );

		/**
		 * Avant exécution manuelle depuis le CRON Command Center.
		 *
		 * @param string $hook Hook.
		 * @param array  $args Arguments.
		 */
		do_action( 'avi_cron_dashboard_before_run_now', $hook, $args );

		do_action_ref_array( $hook, $args );

		$this->redirect_with_arg( 'avi_cron_ran', rawurlencode( $hook ) );
	}

	/**
	 * @param string $key Query arg.
	 * @param string $val Value.
	 * @return void
	 */
	private function redirect_with_arg( $key, $val ) {
		$url = add_query_arg(
			array(
				'page' => self::PAGE_SLUG,
				$key   => $val,
			),
			admin_url( 'admin.php' )
		);
		wp_safe_redirect( $url );
		exit;
	}

	/**
	 * Préfixe le libellé du sous-menu par l’icône dashicons-clock (le cœur WP n’expose pas d’icône native sur les sous-menus).
	 *
	 * @return void
	 */
	public function print_submenu_clock_icon_css() {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		?>
		<style id="avi-cron-submenu-icon">
		#adminmenu .wp-submenu a[href*="page=<?php echo esc_attr( self::PAGE_SLUG ); ?>"]::before {
			font-family: dashicons;
			font-size: 17px;
			line-height: 0;
			margin-right: 4px;
			speak: never;
			-webkit-font-smoothing: antialiased;
			-moz-osx-font-smoothing: grayscale;
			content: "\f469";
			display: inline-block;
			vertical-align: middle;
		}
		</style>
		<?php
	}

	/**
	 * Styles navy / or sur cette page uniquement.
	 *
	 * @param string $hook_suffix Hook écran admin.
	 * @return void
	 */
	public function enqueue_assets( $hook_suffix ) {
		if ( strpos( (string) $hook_suffix, self::PAGE_SLUG ) === false ) {
			return;
		}
		$handle = 'avi-cron-dashboard';
		wp_register_style( $handle, false, array(), '1.0.0' );
		wp_enqueue_style( $handle );
		$css = '
.avi-cc-wrap{max-width:1280px;margin:1.25rem 1.25rem 2rem 0;font-family:system-ui,-apple-system,sans-serif;}
.avi-cc-wrap h1{display:flex;align-items:center;gap:0.5rem;color:#e8ecf4;}
.avi-cc-wrap .avi-cc-lead{color:#94a3b8;font-size:0.95rem;margin:0.35rem 0 1.25rem;}
.avi-cc-card{background:#0f172a;border:1px solid #1e293b;border-radius:12px;padding:1.25rem 1.35rem;margin-bottom:1.5rem;box-shadow:0 8px 32px rgba(0,0,0,0.25);}
.avi-cc-card h2{margin:0 0 0.75rem;font-size:1.05rem;color:#f8fafc;border-bottom:1px solid #334155;padding-bottom:0.5rem;}
.avi-cc-table-wrap{overflow:auto;border-radius:8px;border:1px solid #334155;}
table.avi-cc-table{width:100%;border-collapse:collapse;font-size:0.875rem;}
table.avi-cc-table th{text-align:left;padding:0.65rem 0.75rem;background:#1e293b;color:#cbd5e1;font-weight:600;text-transform:uppercase;letter-spacing:0.04em;font-size:0.72rem;}
table.avi-cc-table td{padding:0.65rem 0.75rem;border-top:1px solid #334155;color:#e2e8f0;vertical-align:middle;}
table.avi-cc-table tbody tr:hover{background:rgba(212,175,55,0.06);}
.avi-cc-code{font-family:ui-monospace,monospace;font-size:0.8rem;color:#fcd34d;word-break:break-all;}
.avi-cc-badge{display:inline-block;padding:0.2rem 0.5rem;border-radius:6px;font-size:0.7rem;font-weight:700;text-transform:uppercase;letter-spacing:0.03em;margin-right:0.25rem;}
.avi-cc-badge--google{background:#1a73e8;color:#fff;}
.avi-cc-badge--gbp{background:#1a73e8;color:#fff;}
.avi-cc-badge--dfs{background:#5b21b6;color:#e9d5ff;}
.avi-cc-badge--report{background:rgba(212,175,55,0.2);color:#fbbf24;border:1px solid rgba(212,175,55,0.45);}
.avi-cc-badge--core{background:#334155;color:#cbd5e1;}
.avi-cc-btn-run{background:linear-gradient(135deg,#b45309,#d97706)!important;border:none!important;color:#fff!important;padding:0.35rem 0.75rem!important;border-radius:6px!important;font-weight:600!important;cursor:pointer;}
.avi-cc-btn-run:hover{filter:brightness(1.08);}
.avi-cc-notice{border-left:4px solid #d4af37;padding:0.65rem 1rem;margin:0 0 1rem;background:#1e293b;color:#e2e8f0;border-radius:0 8px 8px 0;}
.avi-cc-notice--ok{border-left-color:#22c55e;}
.avi-cc-notice--err{border-left-color:#ef4444;}
.avi-cc-email-grid{display:grid;gap:1rem;}
@media(min-width:900px){.avi-cc-email-grid{grid-template-columns:1fr 1fr;}}
.avi-cc-email-block{background:#0a0f18;border:1px solid #334155;border-radius:10px;padding:1rem;}
.avi-cc-email-block h3{margin:0 0 0.5rem;color:#fbbf24;font-size:0.95rem;}
.avi-cc-email-block ul{margin:0;padding-left:1.1rem;color:#cbd5e1;line-height:1.55;font-size:0.85rem;}
.avi-cc-email-block li{margin-bottom:0.35rem;}
.avi-cc-muted{font-size:0.8rem;color:#64748b;margin-top:0.75rem;}
.avi-cc-orchestrator-note{font-size:0.9rem;line-height:1.55;color:#cbd5e1;background:linear-gradient(135deg,rgba(30,58,138,0.35),rgba(15,23,42,0.9));border:1px solid #334155;border-left:4px solid #38bdf8;border-radius:10px;padding:0.85rem 1.1rem;margin:0 0 1.25rem;}
.avi-cc-audit-card ul{margin:0.5rem 0 0;padding-left:1.2rem;color:#cbd5e1;line-height:1.55;font-size:0.88rem;}
.avi-cc-audit-card li{margin-bottom:0.4rem;}
.avi-cc-audit-card code{color:#fcd34d;font-size:0.8rem;}
.avi-cc-cron-section{margin-bottom:1.35rem;}
.avi-cc-cron-section:last-child{margin-bottom:0;}
.avi-cc-cron-section-head{margin:0 0 0.35rem;font-size:1rem;color:#f8fafc;font-weight:700;display:flex;align-items:center;gap:0.35rem;flex-wrap:wrap;}
.avi-cc-cron-section-desc{margin:0 0 0.65rem;font-size:0.82rem;color:#94a3b8;line-height:1.45;}
.avi-cc-hook-subtitle{margin:0.45rem 0 0;font-size:0.82rem;line-height:1.5;color:#94a3b8;font-weight:400;max-width:42rem;}
table.avi-cc-table tbody.avi-cc-tbody-group{border-top:2px solid #475569;}
table.avi-cc-table tr.avi-cc-tr-group td{background:#1e293b;padding:0.75rem 0.85rem!important;border-top:1px solid #475569;font-weight:600;color:#e2e8f0;font-size:0.8rem;text-transform:none;letter-spacing:0;}
';
		wp_add_inline_style( $handle, $css );
	}

	/**
	 * Rendu page.
	 *
	 * @return void
	 */
	public function render_page() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Accès refusé.', 'avision-dashboard' ), 403 );
		}

		$rows      = self::collect_avi_cron_rows();
		$grouped   = self::group_cron_rows_by_api( $rows );
		$grp_order = array( self::GROUP_DATAFORSEO, self::GROUP_GBP, self::GROUP_SAAS, self::GROUP_OTHER );
		$grp_defs  = self::get_cron_group_definitions();
		?>
		<div class="wrap avi-cc-wrap">
			<h1>
				<span class="dashicons dashicons-clock" style="font-size:28px;width:28px;height:28px;color:#d4af37;" aria-hidden="true"></span>
				<?php echo esc_html__( 'Automatisations (CRON)', 'avision-dashboard' ); ?>
			</h1>
			<p class="avi-cc-lead">
				<?php echo esc_html__( 'Tâches WordPress dont le hook commence par avi_ ou avision_ (source : _get_cron_array). « Exécuter maintenant » appelle le hook immédiatement — à utiliser avec prudence en production.', 'avision-dashboard' ); ?>
			</p>
			<p class="avi-cc-orchestrator-note" role="note">
				<?php echo esc_html__( 'Note : Le système utilise un modèle d’Orchestrateur. Le CRON « Hourly Scan » (avision_hourly_scan_orchestrator) déclenche en cascade les 12 points de contrôle du Mode Gardien.', 'avision-dashboard' ); ?>
			</p>

			<?php if ( isset( $_GET['avi_cron_ran'] ) ) : ?>
				<div class="avi-cc-notice avi-cc-notice--ok" role="status">
					<?php
					echo esc_html(
						sprintf(
							/* translators: %s: hook name */
							__( 'Hook exécuté : %s', 'avision-dashboard' ),
							sanitize_text_field( wp_unslash( (string) $_GET['avi_cron_ran'] ) )
						)
					);
					?>
				</div>
			<?php endif; ?>

			<?php if ( isset( $_GET['avi_cron_err'] ) && (string) wp_unslash( $_GET['avi_cron_err'] ) === 'hook' ) : ?>
				<div class="avi-cc-notice avi-cc-notice--err" role="alert">
					<?php echo esc_html__( 'Hook refusé (préfixe non autorisé).', 'avision-dashboard' ); ?>
				</div>
			<?php endif; ?>

			<div class="avi-cc-card">
				<h2><?php echo esc_html__( 'Tâches planifiées AVI', 'avision-dashboard' ); ?></h2>
				<div class="avi-cc-audit-card avi-cc-muted" style="margin-bottom:1rem;">
					<strong style="color:#e2e8f0;"><?php echo esc_html__( 'Audit rapide (code PHP du plugin)', 'avision-dashboard' ); ?></strong>
					<ul>
						<li>
							<?php echo esc_html__( 'Seuls quatre hooks récurrents sont enregistrés via wp_schedule_event :', 'avision-dashboard' ); ?>
							<code>avision_hourly_scan_orchestrator</code> (hourly),
							<code>avision_keyword_scan_queue</code> (every_minute),
							<code>avision_weekly_report</code> (weekly),
							<code>avision_monthly_report</code> (daily).
							<?php echo esc_html__( 'Fichiers :', 'avision-dashboard' ); ?> <code>cron-task-manager.php</code>, <code>class-avision-cron.php</code>.
						</li>
						<li>
							<?php echo esc_html__( 'Pas de hook WP-Cron dédié « Mode Gardien » : la veille et les vagues (Watchers, Creators, Maintainers) sont déclenchées depuis l’orchestrateur horaire lorsque guardian_active est actif.', 'avision-dashboard' ); ?>
						</li>
						<li>
							<?php echo esc_html__( 'Le script Python cron_guardian.py (backend) est documenté comme complément NAP ; il n’est pas planifié par WordPress (pas d’entrée dans _get_cron_array).', 'avision-dashboard' ); ?>
						</li>
						<li>
							<?php echo esc_html__( 'Déclenchement manuel admin :', 'avision-dashboard' ); ?> <code>?avi_cron_run=1</code>
							<?php echo esc_html__( '(Cron_Task_Manager::handle_manual_trigger), distinct du tableau ci-dessous.', 'avision-dashboard' ); ?>
						</li>
					</ul>
				</div>
				<?php if ( empty( $rows ) ) : ?>
					<p class="avi-cc-lead" style="margin:0;"><?php echo esc_html__( 'Aucune tâche AVI dans le cron WordPress pour l’instant (ou WP-Cron désactivé / base vide).', 'avision-dashboard' ); ?></p>
				<?php else : ?>
				<div class="avi-cc-table-wrap">
					<table class="avi-cc-table" cellspacing="0">
						<thead>
							<tr>
								<th><?php echo esc_html__( 'Hook & description', 'avision-dashboard' ); ?></th>
								<th><?php echo esc_html__( 'Cible / type', 'avision-dashboard' ); ?></th>
								<th><?php echo esc_html__( 'Fréquence', 'avision-dashboard' ); ?></th>
								<th><?php echo esc_html__( 'Prochaine exécution', 'avision-dashboard' ); ?></th>
								<th><?php echo esc_html__( 'Action', 'avision-dashboard' ); ?></th>
							</tr>
						</thead>
						<?php foreach ( $grp_order as $gid ) : ?>
							<?php
							if ( empty( $grouped[ $gid ] ) ) {
								continue;
							}
							$gmeta = isset( $grp_defs[ $gid ] ) ? $grp_defs[ $gid ] : $grp_defs[ self::GROUP_OTHER ];
							?>
							<tbody class="avi-cc-tbody-group">
								<tr class="avi-cc-tr-group">
									<td colspan="5">
										<?php echo esc_html( $gmeta['title'] ); ?>
										<?php if ( ! empty( $gmeta['description'] ) ) : ?>
											<span style="font-weight:400;color:#94a3b8;"> — <?php echo esc_html( $gmeta['description'] ); ?></span>
										<?php endif; ?>
									</td>
								</tr>
								<?php foreach ( $grouped[ $gid ] as $r ) : ?>
									<?php
									$next       = wp_date( 'Y-m-d H:i:s', $r['timestamp'] );
									$args_json  = wp_json_encode( $r['args'] );
									$hook_sub   = self::get_hook_table_subtitle( $r['hook'] );
									?>
									<tr>
										<td>
											<span class="avi-cc-code"><?php echo esc_html( $r['hook'] ); ?></span>
											<?php if ( $hook_sub !== '' ) : ?>
												<p class="avi-cc-hook-subtitle"><?php echo esc_html( $hook_sub ); ?></p>
											<?php endif; ?>
											<?php if ( ! empty( $r['args'] ) ) : ?>
												<div class="avi-cc-muted"><?php echo esc_html__( 'Args :', 'avision-dashboard' ); ?> <code style="color:#94a3b8;"><?php echo esc_html( $args_json ); ?></code></div>
											<?php endif; ?>
										</td>
										<td><?php echo wp_kses( self::target_badges_html( $r['hook'] ), array( 'span' => array( 'class' => true ) ) ); ?></td>
										<td><?php echo esc_html( $r['schedule_label'] ); ?></td>
										<td><?php echo esc_html( $next ); ?></td>
										<td>
											<form method="post" action="<?php echo esc_url( admin_url( 'admin.php?page=' . self::PAGE_SLUG ) ); ?>" style="margin:0;">
												<?php wp_nonce_field( 'avi_cron_dashboard_run_now' ); ?>
												<input type="hidden" name="avi_cron_dashboard_action" value="run_now" />
												<input type="hidden" name="avi_cron_dashboard_page" value="<?php echo esc_attr( self::PAGE_SLUG ); ?>" />
												<input type="hidden" name="hook" value="<?php echo esc_attr( $r['hook'] ); ?>" />
												<input type="hidden" name="args_json" value="<?php echo esc_attr( $args_json ); ?>" />
												<button type="submit" class="button avi-cc-btn-run"><?php echo esc_html__( 'Exécuter maintenant', 'avision-dashboard' ); ?></button>
											</form>
										</td>
									</tr>
								<?php endforeach; ?>
							</tbody>
						<?php endforeach; ?>
					</table>
				</div>
				<?php endif; ?>
			</div>

			<div class="avi-cc-card">
				<h2><?php echo esc_html__( 'Emails membres — inventaire (B2B / B2C)', 'avision-dashboard' ); ?></h2>
				<p class="avi-cc-lead" style="margin-top:0;">
					<?php echo esc_html__( 'Référence des envois présents dans le code du plugin (Brevo, wp_mail, WordPress). Les déclencheurs exacts peuvent dépendre du parcours utilisateur et des options actives.', 'avision-dashboard' ); ?>
				</p>
				<div class="avi-cc-email-grid">
					<div class="avi-cc-email-block">
						<h3><?php echo esc_html__( 'B2C — Cockpit & parcours', 'avision-dashboard' ); ?></h3>
						<ul>
							<li><strong>Brevo template 41</strong> — <?php echo esc_html__( 'Bienvenue + lien d’activation / magic link (aucun mot de passe en clair).', 'avision-dashboard' ); ?> <code>Avision_Email_Manager::send_avi_email</code>, <?php echo esc_html__( 'flux inscription / Stripe.', 'avision-dashboard' ); ?></li>
							<li><strong>wp_mail</strong> — <?php echo esc_html__( 'Email « Cockpit prêt » après paiement si compte WP existant (lien activation optionnel).', 'avision-dashboard' ); ?> <code>avision_send_welcome_b2c_email_checkout_paid_existing_wp_user</code> — <code>avision-dashboard.php</code></li>
							<li><strong>OUT_OF_INFUSION</strong> — <?php echo esc_html__( 'Données J0 prêtes (fin première infusion / scan).', 'avision-dashboard' ); ?> <code>Avision_Email_Manager</code> — <?php echo esc_html__( 'appel depuis', 'avision-dashboard' ); ?> <code>class-avision-database.php</code></li>
							<li><strong>GUARDIAN_PURCHASE</strong> — <?php echo esc_html__( 'Confirmation activation Mode Gardien.', 'avision-dashboard' ); ?></li>
							<li><strong>REPORTS</strong> — <?php echo esc_html__( 'Rapport disponible (fallback HTML / Brevo).', 'avision-dashboard' ); ?></li>
							<li><strong>WELCOME / TIMER_DONE / SPRINT_UNLOCK / L21_FIRST_TIME</strong> — <?php echo esc_html__( 'Parcours gamifié (infusion, déblocage sprint, fin L21).', 'avision-dashboard' ); ?> <code>Avision_Transactional_Emails</code></li>
							<li><strong>Mode Gardien — notifications</strong> — <?php echo esc_html__( 'Rapport hebdomadaire, rapport mensuel, alerte crise avis ≤2★, confirmation neutralisation.', 'avision-dashboard' ); ?> <code>Avision_Notifications</code></li>
							<li><strong>Hub validation avis</strong> — <?php echo esc_html__( 'Proposition de réponse à valider, alertes sensibilité, accusé / récap réponse publiée.', 'avision-dashboard' ); ?> <code>class-guardian-mode.php</code> (wp_mail)</li>
							<li><strong>Mot de passe oublié</strong> — <?php echo esc_html__( 'Email HTML WordPress (filtres dédiés).', 'avision-dashboard' ); ?> <code>class-avision-auth-shortcodes.php</code></li>
						</ul>
					</div>
					<div class="avi-cc-email-block">
						<h3><?php echo esc_html__( 'B2B — Agences & reporting', 'avision-dashboard' ); ?></h3>
						<ul>
							<li><?php echo esc_html__( 'Pas de template Brevo séparé « B2B only » dans Avision_Email_Manager : les envois transactionnels listés à gauche s’appliquent selon le profil / audit (e-mail du membre rattaché).', 'avision-dashboard' ); ?></li>
							<li><?php echo esc_html__( 'Le ton « Fleet Coach » et le gabarit reporting agence sont portés par le Knowledge Center (prompts', 'avision-dashboard' ); ?> <code>avi_prompt_b2b_welcome</code>, <code>avi_prompt_b2b_global</code>) <?php echo esc_html__( '— pas des modèles d’e-mail distincts dans le code actuel.', 'avision-dashboard' ); ?></li>
							<li><?php echo esc_html__( 'Si des rapports ou alertes sont générés pour des comptes agence, ils empruntent les mêmes classes', 'avision-dashboard' ); ?> (<code>Avision_Notifications</code>, <code>class-guardian-mode.php</code>) <?php echo esc_html__( 'vers l’adresse e-mail du profil audit.', 'avision-dashboard' ); ?></li>
							<li><?php echo esc_html__( 'Service générique', 'avision-dashboard' ); ?> <code>includes/services/class-emailer.php</code> <?php echo esc_html__( '(wp_mail) pour extensions internes.', 'avision-dashboard' ); ?></li>
						</ul>
					</div>
				</div>
				<p class="avi-cc-muted">
					<?php echo esc_html__( 'Pour ajouter un type d’e-mail : étendre Avision_Email_Manager (Brevo) ou documenter ici les nouveaux wp_mail.', 'avision-dashboard' ); ?>
				</p>
			</div>
		</div>
		<?php
	}
}
