<?php
/**
 * AVI Code Guard Center — intégrité fichiers, snapshots git, rollbacks.
 *
 * @package Avision
 */

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

/**
 * Panneau admin Code Guard (onglet Control Center).
 */
class Avision_Code_Guard_Center {

	const NONCE_ACTION = 'avision_cg_nonce';
	const ROLLBACK_LOG_OPTION = 'avi_cg_rollback_log';
	const ROLLBACK_LOG_MAX = 50;
	const SNAPSHOTS_GIT_DIR = '/root/prod-snapshots';
	const CHECKSUMS_MD5_PATH = '/root/prod-snapshots/checksums.md5';

	/**
	 * @return void
	 */
	public static function init() {
		add_action( 'wp_ajax_avision_cg_get_metrics', array( __CLASS__, 'handle_get_metrics' ) );
		add_action( 'wp_ajax_avision_cg_get_snapshots', array( __CLASS__, 'handle_get_snapshots' ) );
		add_action( 'wp_ajax_avision_cg_get_diff', array( __CLASS__, 'handle_get_diff' ) );
		add_action( 'wp_ajax_avision_cg_rollback', array( __CLASS__, 'handle_rollback' ) );
		add_action( 'wp_ajax_avision_cg_take_snapshot', array( __CLASS__, 'handle_take_snapshot' ) );
		add_action( 'wp_ajax_avision_cg_update_checksums', array( __CLASS__, 'handle_update_checksums' ) );
		add_action( 'wp_ajax_avision_cg_post_deploy', array( __CLASS__, 'handle_post_deploy' ) );
	}

	/**
	 * 17 fichiers critiques (basename) autorisés pour diff / rollback.
	 *
	 * @return array<int, string>
	 */
	public static function file_whitelist() {
		return array(
			'page-premium-dashboard.php',
			'page-agency-dashboard.php',
			'functions.php',
			'page-btob-onboarding.php',
			'proxy-avi.php',
			'class-avision-api.php',
			'class-ajax-router.php',
			'class-avision-stripe-webhook.php',
			'class-avision-dashboard-route-guard.php',
			'avi-stripe-s10b.php',
			'class-avision-control-center.php',
			'avision-dashboard.php',
			'premium-dashboard.php',
			'premium-dashboard-v4.js',
			'agency-dashboard.js',
			'main.py',
			'orchestrator.py',
			'rag_retrieval.py',
			'legal-fr.php',
			'legal-es.php',
			'partners.php',
		);
	}

	/**
	 * @param string $file
	 * @return bool
	 */
	private static function is_file_whitelisted( $file ) {
		$file = basename( sanitize_file_name( (string) $file ) );
		return $file !== '' && in_array( $file, self::file_whitelist(), true );
	}

	/**
	 * @return void
	 */
	private static function verify_ajax_request() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => 'forbidden' ), 403 );
		}
		check_ajax_referer( self::NONCE_ACTION, 'nonce' );
	}

	/**
	 * @param string $cmd
	 * @return string
	 */
	private static function run_bash( $cmd ) {
		$out = shell_exec( preg_replace( '/^bash \/root\//', 'sudo bash /root/', $cmd ) );
		return is_string( $out ) ? $out : '';
	}

	/**
	 * @return array<string, int>
	 */
	private static function load_ref_lines_from_checksums() {
		$ref = array();
		$path = self::CHECKSUMS_MD5_PATH;
		if ( ! is_readable( $path ) ) {
			$alt = '/root/scripts/checksums.md5';
			if ( is_readable( $alt ) ) {
				$path = $alt;
			} else {
				return $ref;
			}
		}
		$raw = file_get_contents( $path );
		if ( ! is_string( $raw ) || $raw === '' ) {
			return $ref;
		}
		foreach ( preg_split( '/\r\n|\r|\n/', $raw ) as $line ) {
			$line = trim( $line );
			if ( $line === '' || strpos( $line, '#' ) === 0 ) {
				continue;
			}
			// Format attendu : lignes|poids|fichier ou md5  file
			if ( preg_match( '/^(\d+)\|(\d+)\|(.+)$/', $line, $m ) ) {
				$base = basename( trim( $m[3] ) );
				if ( $base !== '' ) {
					$ref[ $base ] = (int) $m[1];
				}
				continue;
			}
			if ( preg_match( '/^([a-f0-9]{32})\s{2,}(.+)$/i', $line, $m ) ) {
				$base = basename( trim( $m[2] ) );
				if ( $base !== '' && ! isset( $ref[ $base ] ) ) {
					$snap_file = self::SNAPSHOTS_GIT_DIR . '/plugin/' . $base;
					if ( ! file_exists( $snap_file ) ) {
						$snap_file = self::SNAPSHOTS_GIT_DIR . '/theme/' . $base;
					}
					if ( ! file_exists( $snap_file ) ) {
						$snap_file = self::SNAPSHOTS_GIT_DIR . '/python/' . $base;
					}
					$git_lines = file_exists( $snap_file )
						? (string) substr_count( file_get_contents( $snap_file ), "\n" )
						: '0';
					$n = (int) trim( $git_lines );
					$ref[ $base ] = $n > 0 ? $n : 0;
				}
			}
		}
		return $ref;
	}

	/**
	 * @return void
	 */
	public static function handle_get_metrics() {
		self::verify_ajax_request();
		$output = self::run_bash( 'bash /root/scripts/file_metrics.sh 2>/dev/null' );
		$data = json_decode( $output, true );
		if ( ! is_array( $data ) ) {
			$data = array();
		}
		unset( $data['ref_lines'], $data['last_check'] );
		$ref_lines = self::load_ref_lines_from_checksums();
		wp_send_json_success(
			array(
				'files'      => array_values( $data ),
				'ref_lines'  => $ref_lines,
				'last_check' => gmdate( 'c' ),
			)
		);
	}

	/**
	 * @return void
	 */
	public static function handle_get_snapshots() {
		self::verify_ajax_request();
		$raw = self::run_bash( 'git config --global --add safe.directory /root/prod-snapshots 2>/dev/null; git -C /root/prod-snapshots log --oneline --format="%H|%s|%ci" -20 2>/dev/null' );
		if ( trim( $raw ) === '' ) {
			wp_send_json_success( array( 'snapshots' => array() ) );
		}
		$list = array();
		foreach ( preg_split( '/\r\n|\r|\n/', trim( $raw ) ) as $line ) {
			$line = trim( $line );
			if ( $line === '' ) {
				continue;
			}
			$parts = explode( '|', $line, 3 );
			if ( count( $parts ) < 3 ) {
				continue;
			}
			$list[] = array(
				'hash'  => $parts[0],
				'label' => $parts[1],
				'date'  => $parts[2],
			);
		}
		wp_send_json_success( array( 'snapshots' => $list ) );
	}

	/**
	 * @return void
	 */
	public static function handle_get_diff() {
		self::verify_ajax_request();
		$file     = isset( $_POST['file'] ) ? sanitize_file_name( wp_unslash( $_POST['file'] ) ) : '';
		$commit1  = isset( $_POST['commit1'] ) ? sanitize_text_field( wp_unslash( $_POST['commit1'] ) ) : '';
		$commit2  = isset( $_POST['commit2'] ) ? sanitize_text_field( wp_unslash( $_POST['commit2'] ) ) : '';
		if ( ! self::is_file_whitelisted( $file ) ) {
			wp_send_json_error( array( 'message' => 'invalid_file' ), 400 );
		}
		if ( ! preg_match( '/^[a-f0-9]{7,40}$/i', $commit1 ) || ! preg_match( '/^[a-f0-9]{7,40}$/i', $commit2 ) ) {
			wp_send_json_error( array( 'message' => 'invalid_commit' ), 400 );
		}
		$diff = self::run_bash(
			'bash /root/scripts/diff_snapshot.sh '
			. escapeshellarg( $file ) . ' '
			. escapeshellarg( $commit1 ) . ' '
			. escapeshellarg( $commit2 ) . ' 2>/dev/null'
		);
		wp_send_json_success(
			array(
				'diff' => $diff,
				'file' => $file,
			)
		);
	}

	/**
	 * @return void
	 */
	public static function handle_rollback() {
		self::verify_ajax_request();
		$file = isset( $_POST['file'] ) ? sanitize_file_name( wp_unslash( $_POST['file'] ) ) : '';
		if ( ! self::is_file_whitelisted( $file ) ) {
			wp_send_json_error( array( 'message' => 'invalid_file' ), 400 );
		}
		$output  = self::run_bash( 'bash /root/scripts/rollback_file.sh ' . escapeshellarg( $file ) . ' 2>/dev/null' );
		$success = is_string( $output ) && stripos( $output, 'error' ) === false && stripos( $output, 'échec' ) === false;
		$user    = wp_get_current_user();
		$entry   = array(
			'date'   => gmdate( 'Y-m-d H:i:s' ),
			'file'   => $file,
			'user'   => $user && $user->exists() ? $user->user_login : 'admin',
			'result' => $success ? 'ok' : 'fail',
			'output' => substr( (string) $output, 0, 500 ),
		);
		$log = get_option( self::ROLLBACK_LOG_OPTION, array() );
		if ( ! is_array( $log ) ) {
			$log = array();
		}
		array_unshift( $log, $entry );
		$log = array_slice( $log, 0, self::ROLLBACK_LOG_MAX );
		update_option( self::ROLLBACK_LOG_OPTION, $log, false );
		if ( $success ) {
			wp_send_json_success( array( 'message' => 'rollback_ok', 'output' => $output ) );
		}
		wp_send_json_error( array( 'message' => 'rollback_failed', 'output' => $output ), 500 );
	}

	/**
	 * @return void
	 */
	public static function handle_take_snapshot() {
		self::verify_ajax_request();
		$label = isset( $_POST['label'] ) ? sanitize_text_field( wp_unslash( $_POST['label'] ) ) : '';
		if ( $label === '' ) {
			$label = 'snapshot-' . gmdate( 'Ymd-His' );
		}
		$output = self::run_bash( 'bash /root/scripts/git_sync_prod.sh ' . escapeshellarg( $label ) . ' 2>/dev/null' );
		wp_send_json_success( array( 'output' => $output, 'label' => $label ) );
	}

	/**
	 * @return void
	 */
	public static function handle_update_checksums() {
		self::verify_ajax_request();
		$output = self::run_bash( 'bash /root/scripts/generate_checksums.sh 2>/dev/null' );
		wp_send_json_success( array( 'output' => $output ) );
	}

	/**
	 * @return void
	 */
	public static function handle_post_deploy() {
		self::verify_ajax_request();
		$label = isset( $_POST['label'] ) ? sanitize_text_field( wp_unslash( $_POST['label'] ) ) : 'post-deploy-' . date( 'Ymd-Hi' );
		$snap  = self::run_bash( 'bash /root/scripts/git_sync_prod.sh ' . escapeshellarg( $label ) . ' 2>/dev/null' );
		$check = self::run_bash( 'bash /root/scripts/generate_checksums.sh 2>/dev/null' );
		$gate  = self::run_bash( 'bash /root/scripts/deploy_gate.sh 2>/dev/null' );
		$passed = strpos( $gate, 'PASSED' ) !== false;
		wp_send_json_success(
			array(
				'snapshot'  => trim( $snap ),
				'checksums' => trim( $check ),
				'gate'      => trim( $gate ),
				'passed'    => $passed,
			)
		);
	}

	/**
	 * @return array<int, array<string, string>>
	 */
	private static function get_rollback_log_for_display() {
		$log = get_option( self::ROLLBACK_LOG_OPTION, array() );
		if ( ! is_array( $log ) ) {
			return array();
		}
		return array_slice( $log, 0, 20 );
	}

	/**
	 * @return void
	 */
	public static function render_tab() {
		if ( ! current_user_can( 'manage_options' ) ) {
			echo '<p>' . esc_html__( 'Accès refusé.', 'avision-dashboard' ) . '</p>';
			return;
		}
		$nonce    = wp_create_nonce( self::NONCE_ACTION );
		$ajax_url = admin_url( 'admin-ajax.php' );
		$rollback_log = self::get_rollback_log_for_display();
		$file_list_json = wp_json_encode( self::file_whitelist() );
		?>
		<style id="avi-cg-styles">
		#avi-cg-wrap { font-family: Inter, sans-serif; }
		#avi-cg-wrap .avi-cg-toolbar { display:flex; gap:10px; align-items:center; margin-bottom:20px; flex-wrap:wrap; }
		#avi-cg-wrap .avi-cg-toolbar button { background:#1e3a5f; color:#fff; border:none; padding:8px 16px; border-radius:6px; cursor:pointer; font-size:13px; }
		#avi-cg-wrap .avi-cg-toolbar button:hover { background:#2d4a6f; }
		#avi-cg-wrap .avi-cg-toolbar #avi-cg-last-check { font-size:12px; color:#64748b; margin-left:auto; }
		#avi-cg-wrap .avi-cg-badge-count { background:#dc2626; color:#fff; padding:4px 10px; border-radius:999px; font-size:12px; font-weight:700; margin-bottom:12px; display:inline-block; }
		#avi-cg-wrap .avi-cg-table { width:100%; border-collapse:collapse; font-size:13px; margin-bottom:24px; }
		#avi-cg-wrap .avi-cg-table th { background:#0f2744; color:#fff; padding:10px; text-align:left; }
		#avi-cg-wrap .avi-cg-table td { padding:9px 10px; border-bottom:1px solid #e8ecf0; }
		#avi-cg-wrap .avi-cg-ok { color:#16a34a; font-weight:600; }
		#avi-cg-wrap .avi-cg-warn { color:#d97706; font-weight:600; }
		#avi-cg-wrap .avi-cg-error { color:#dc2626; font-weight:600; }
		#avi-cg-wrap .avi-cg-delta-neg { color:#dc2626; }
		#avi-cg-wrap .avi-cg-delta-pos { color:#16a34a; }
		#avi-cg-wrap .avi-cg-section-title { font-size:15px; font-weight:700; color:#0f2744; margin:24px 0 12px; }
		#avi-cg-wrap .avi-cg-modal { display:none; position:fixed; inset:0; background:rgba(0,0,0,.6); z-index:99999; }
		#avi-cg-wrap .avi-cg-modal.is-open { display:block; }
		#avi-cg-wrap .avi-cg-modal-inner { background:#fff; border-radius:12px; max-width:900px; max-height:80vh; overflow:auto; margin:60px auto; padding:30px; position:relative; box-shadow:0 24px 64px rgba(0,0,0,.35); }
		#avi-cg-wrap .avi-cg-modal-close { position:absolute; top:12px; right:16px; background:transparent; border:none; font-size:22px; cursor:pointer; color:#64748b; }
		#avi-cg-wrap .avi-cg-diff-pre { font-family:ui-monospace,monospace; font-size:12px; line-height:1.45; white-space:pre-wrap; }
		#avi-cg-wrap .avi-cg-diff-add { background:#dcfce7; color:#166534; display:block; }
		#avi-cg-wrap .avi-cg-diff-del { background:#fee2e2; color:#991b1b; display:block; }
		#avi-cg-wrap .avi-cg-diff-ctx { color:#6b7280; display:block; }
		#avi-cg-wrap .avi-cg-btn-sm { background:#6366f1; color:#fff; border:none; padding:4px 10px; border-radius:4px; font-size:12px; cursor:pointer; }
		#avi-cg-wrap .avi-cg-btn-rollback { background:#f4b942; color:#0a1628; font-weight:600; }
		</style>
		<div id="avi-cg-wrap">
			<p class="description"><?php esc_html_e( 'Surveillance des fichiers critiques (scripts /root). Réservé aux administrateurs.', 'avision-dashboard' ); ?></p>
			<span id="avi-cg-nonconform-badge" class="avi-cg-badge-count" style="display:none;">0 <?php esc_html_e( 'fichier(s) non conforme(s)', 'avision-dashboard' ); ?></span>

			<div class="avi-cg-toolbar">
				<button type="button" id="avi-cg-btn-snapshot" title="Crée un snapshot git horodaté — à faire avant chaque modification importante"><?php esc_html_e( '📸 Snapshot maintenant', 'avision-dashboard' ); ?></button>
				<button type="button" id="avi-cg-btn-refresh" title="Recharge les métriques et la liste des snapshots sans recharger la page"><?php esc_html_e( '🔄 Actualiser', 'avision-dashboard' ); ?></button>
				<button type="button" id="avi-cg-btn-checksums" title="Met à jour la référence md5 — à faire APRÈS un déploiement validé pour éviter les fausses alertes"><?php esc_html_e( '🔒 Mettre à jour référence', 'avision-dashboard' ); ?></button>
				<button type="button" id="avi-cg-btn-post-deploy" title="Lance smoke test + Code Guard + snapshot — à exécuter après chaque déploiement WinSCP" style="background:#16a34a;"><?php esc_html_e( '✅ Valider déploiement', 'avision-dashboard' ); ?></button>
				<span id="avi-cg-deploy-status" style="font-size:12px;color:#6b7280;margin-left:10px;"></span>
				<span id="avi-cg-last-check"><?php esc_html_e( 'Dernier check :', 'avision-dashboard' ); ?> —</span>
			</div>

			<h3 class="avi-cg-section-title"><?php esc_html_e( 'Intégrité des fichiers', 'avision-dashboard' ); ?></h3>
			<table class="avi-cg-table" id="avi-cg-metrics-table">
				<thead>
					<tr>
						<th><?php esc_html_e( 'Fichier', 'avision-dashboard' ); ?></th>
						<th><?php esc_html_e( 'Lignes', 'avision-dashboard' ); ?></th>
						<th><?php esc_html_e( 'Poids', 'avision-dashboard' ); ?></th>
						<th><?php esc_html_e( 'Delta vs référence', 'avision-dashboard' ); ?></th>
						<th><?php esc_html_e( 'Intégrité', 'avision-dashboard' ); ?></th>
						<th><?php esc_html_e( 'Dernière modif', 'avision-dashboard' ); ?></th>
						<th><?php esc_html_e( 'Action', 'avision-dashboard' ); ?></th>
					</tr>
				</thead>
				<tbody id="avi-cg-metrics-body">
					<tr><td colspan="7"><?php esc_html_e( 'Chargement…', 'avision-dashboard' ); ?></td></tr>
				</tbody>
			</table>

			<h3 class="avi-cg-section-title"><?php esc_html_e( 'Timeline snapshots', 'avision-dashboard' ); ?></h3>
			<table class="avi-cg-table" id="avi-cg-snapshots-table">
				<thead>
					<tr>
						<th><?php esc_html_e( 'Date', 'avision-dashboard' ); ?></th>
						<th><?php esc_html_e( 'Label', 'avision-dashboard' ); ?></th>
						<th><?php esc_html_e( 'Action', 'avision-dashboard' ); ?></th>
					</tr>
				</thead>
				<tbody id="avi-cg-snapshots-body">
					<tr><td colspan="3"><?php esc_html_e( 'Chargement…', 'avision-dashboard' ); ?></td></tr>
				</tbody>
			</table>

			<h3 class="avi-cg-section-title"><?php esc_html_e( 'Log rollbacks', 'avision-dashboard' ); ?></h3>
			<table class="avi-cg-table">
				<thead>
					<tr>
						<th><?php esc_html_e( 'Date', 'avision-dashboard' ); ?></th>
						<th><?php esc_html_e( 'Fichier', 'avision-dashboard' ); ?></th>
						<th><?php esc_html_e( 'Utilisateur', 'avision-dashboard' ); ?></th>
						<th><?php esc_html_e( 'Résultat', 'avision-dashboard' ); ?></th>
					</tr>
				</thead>
				<tbody>
					<?php if ( empty( $rollback_log ) ) : ?>
						<tr><td colspan="4"><?php esc_html_e( 'Aucun rollback enregistré.', 'avision-dashboard' ); ?></td></tr>
					<?php else : ?>
						<?php foreach ( $rollback_log as $row ) : ?>
							<tr>
								<td><?php echo esc_html( isset( $row['date'] ) ? (string) $row['date'] : '' ); ?></td>
								<td><code><?php echo esc_html( isset( $row['file'] ) ? (string) $row['file'] : '' ); ?></code></td>
								<td><?php echo esc_html( isset( $row['user'] ) ? (string) $row['user'] : '' ); ?></td>
								<td class="<?php echo ( isset( $row['result'] ) && $row['result'] === 'ok' ) ? 'avi-cg-ok' : 'avi-cg-error'; ?>">
									<?php echo esc_html( isset( $row['result'] ) ? (string) $row['result'] : '' ); ?>
								</td>
							</tr>
						<?php endforeach; ?>
					<?php endif; ?>
				</tbody>
			</table>
		</div>

		<div id="avi-cg-diff-modal" class="avi-cg-modal" aria-hidden="true">
			<div class="avi-cg-modal-inner">
				<button type="button" class="avi-cg-modal-close" id="avi-cg-modal-close" aria-label="<?php esc_attr_e( 'Fermer', 'avision-dashboard' ); ?>">&times;</button>
				<h3><?php esc_html_e( 'Diff snapshot', 'avision-dashboard' ); ?></h3>
				<p>
					<label for="avi-cg-diff-file"><?php esc_html_e( 'Fichier', 'avision-dashboard' ); ?></label>
					<select id="avi-cg-diff-file" style="min-width:280px;margin-left:8px;"></select>
				</p>
				<div id="avi-cg-diff-output" class="avi-cg-diff-pre"></div>
			</div>
		</div>

		<script>
		(function () {
			var ajaxUrl = <?php echo wp_json_encode( $ajax_url ); ?>;
			var nonce = <?php echo wp_json_encode( $nonce ); ?>;
			var aviCgNonce = nonce;
			var whitelist = <?php echo $file_list_json ? $file_list_json : '[]'; ?>;
			var snapshotsCache = [];
			var diffCommits = { c1: '', c2: '' };

			function esc(s) {
				var d = document.createElement('div');
				d.textContent = s == null ? '' : String(s);
				return d.innerHTML;
			}

			function post(action, data) {
				var params = { action: action, nonce: nonce };
				if (data) {
					Object.keys(data).forEach(function (k) { params[k] = data[k]; });
				}
				return new Promise(function (resolve, reject) {
					jQuery.post(ajaxUrl, params)
						.done(function (res) { resolve(res); })
						.fail(function (xhr) { reject(new Error('AJAX ' + xhr.status)); });
				});
			}

			function formatDelta(current, ref) {
				if (ref == null || ref === undefined || ref === '') {
					return { html: '—', cls: '', bad: false };
				}
				ref = parseInt(ref, 10);
				current = parseInt(current, 10);
				if (isNaN(current) || current < 0) {
					return { html: '<span class="avi-cg-error">manquant</span>', cls: 'avi-cg-error', bad: true };
				}
				var delta = current - ref;
				if (delta === 0) {
					return { html: '<span class="avi-cg-delta-pos">0</span>', cls: 'avi-cg-ok', bad: false };
				}
				var pct = ref > 0 ? Math.round((Math.abs(delta) / ref) * 100) : 100;
				if (delta < 0 && pct >= 10) {
					return { html: '<span class="avi-cg-delta-neg">' + delta + ' (' + pct + '%)</span>', cls: 'avi-cg-error', bad: true };
				}
				if (delta < 0) {
					return { html: '<span class="avi-cg-warn">' + delta + ' (' + pct + '%)</span>', cls: 'avi-cg-warn', bad: false };
				}
				return { html: '<span class="avi-cg-delta-pos">+' + delta + '</span>', cls: 'avi-cg-ok', bad: false };
			}

			function loadMetrics() {
				post('avision_cg_get_metrics').then(function (res) {
					var tbody = document.getElementById('avi-cg-metrics-body');
					if (!tbody) return;
					var payload = res.data || res;
					var files = [];
					if (Array.isArray(payload.files)) {
						files = payload.files;
					} else if (Array.isArray(payload)) {
						files = payload;
					}
					var refLines = payload.ref_lines || {};
					var badCount = 0;
					var html = '';
					files.forEach(function (row) {
						var name = row.file || row.name || '';
						var lines = row.lines != null ? row.lines : row.line_count;
						var size = row.size || row.weight || row.bytes || '—';
						var mod = row.modified || row.mtime || row.last_modified || '—';
						var ref = refLines[name];
						var d = formatDelta(lines, ref);
						if (d.bad) badCount++;
						var integrity = d.bad ? '<span class="avi-cg-error">⚠</span>' : '<span class="avi-cg-ok">✓</span>';
						html += '<tr data-file="' + esc(name) + '">';
						html += '<td><code>' + esc(name) + '</code></td>';
						html += '<td>' + esc(lines) + '</td>';
						html += '<td>' + esc(size) + '</td>';
						html += '<td>' + d.html + '</td>';
						html += '<td class="' + d.cls + '">' + integrity + '</td>';
						html += '<td>' + esc(mod) + '</td>';
						html += '<td><button type="button" class="avi-cg-btn-sm avi-cg-btn-rollback" data-file="' + esc(name) + '">Rollback</button></td>';
						html += '</tr>';
					});
					tbody.innerHTML = html || '<tr><td colspan="7">Aucune métrique.</td></tr>';
					var badge = document.getElementById('avi-cg-nonconform-badge');
					if (badge) {
						if (badCount > 0) {
							badge.style.display = 'inline-block';
							badge.textContent = badCount + ' fichier(s) non conforme(s)';
						} else {
							badge.style.display = 'none';
						}
					}
					var lc = document.getElementById('avi-cg-last-check');
					if (lc && payload && payload.last_check) {
						lc.textContent = '<?php echo esc_js( __( 'Dernier check :', 'avision-dashboard' ) ); ?> ' + payload.last_check;
					}
					tbody.querySelectorAll('.avi-cg-btn-rollback').forEach(function (btn) {
						btn.addEventListener('click', function () {
							rollbackFile(btn.getAttribute('data-file'));
						});
					});
				}).catch(function () {
					var tbody = document.getElementById('avi-cg-metrics-body');
					if (tbody) tbody.innerHTML = '<tr><td colspan="7">Erreur chargement métriques.</td></tr>';
				});
			}

			function loadSnapshots() {
				post('avision_cg_get_snapshots').then(function (res) {
					var tbody = document.getElementById('avi-cg-snapshots-body');
					if (!tbody) return;
					snapshotsCache = (res.data && Array.isArray(res.data.snapshots)) ? res.data.snapshots : [];
					if (!snapshotsCache.length) {
						tbody.innerHTML = '<tr><td colspan="3">Aucun snapshot.</td></tr>';
						return;
					}
					var html = '';
					snapshotsCache.forEach(function (snap, idx) {
						html += '<tr>';
						html += '<td>' + esc(snap.date) + '</td>';
						html += '<td>' + esc(snap.label) + '</td>';
						html += '<td><button type="button" class="avi-cg-btn-sm avi-cg-btn-diff" data-idx="' + idx + '">Voir diff</button></td>';
						html += '</tr>';
					});
					tbody.innerHTML = html;
					tbody.querySelectorAll('.avi-cg-btn-diff').forEach(function (btn) {
						btn.addEventListener('click', function () {
							var i = parseInt(btn.getAttribute('data-idx'), 10);
							var snap = snapshotsCache[i];
							if (!snap) return;
							var prev = snapshotsCache[i + 1];
							diffCommits.c1 = prev ? prev.hash : snap.hash;
							diffCommits.c2 = snap.hash;
							openDiffModal();
						});
					});
				});
			}

			function parseDiffHtml(text) {
				var out = '';
				String(text || '').split('\n').forEach(function (line) {
					if (line.indexOf('+++') === 0 || line.indexOf('---') === 0) {
						out += '<span class="avi-cg-diff-ctx">' + esc(line) + '</span>';
					} else if (line.indexOf('+') === 0) {
						out += '<span class="avi-cg-diff-add">' + esc(line) + '</span>';
					} else if (line.indexOf('-') === 0) {
						out += '<span class="avi-cg-diff-del">' + esc(line) + '</span>';
					} else {
						out += '<span class="avi-cg-diff-ctx">' + esc(line) + '</span>';
					}
				});
				return out;
			}

			function showDiff(file, commit1, commit2) {
				post('avision_cg_get_diff', { file: file, commit1: commit1, commit2: commit2 }).then(function (res) {
					var el = document.getElementById('avi-cg-diff-output');
					if (!el) return;
					if (res && res.success && res.data && res.data.diff != null) {
						el.innerHTML = parseDiffHtml(res.data.diff);
					} else {
						el.textContent = (res && res.data && res.data.message) ? res.data.message : 'Diff indisponible.';
					}
				});
			}

			function openDiffModal() {
				var modal = document.getElementById('avi-cg-diff-modal');
				var sel = document.getElementById('avi-cg-diff-file');
				if (!modal || !sel) return;
				sel.innerHTML = '';
				whitelist.forEach(function (f) {
					var o = document.createElement('option');
					o.value = f;
					o.textContent = f;
					sel.appendChild(o);
				});
				modal.classList.add('is-open');
				modal.setAttribute('aria-hidden', 'false');
				showDiff(sel.value, diffCommits.c1, diffCommits.c2);
				sel.onchange = function () {
					showDiff(sel.value, diffCommits.c1, diffCommits.c2);
				};
			}

			function rollbackFile(file) {
				if (!file || !window.confirm('Rollback de ' + file + ' ?')) return;
				post('avision_cg_rollback', { file: file }).then(function (res) {
					if (res && res.success) {
						window.alert('Rollback réussi.');
						window.location.reload();
					} else {
						window.alert('Échec rollback : ' + (res && res.data && res.data.output ? res.data.output : 'erreur'));
					}
				});
			}

			function takeSnapshot() {
				var label = window.prompt('Label du snapshot :', 'manual-' + new Date().toISOString().slice(0, 16));
				if (label === null) return;
				post('avision_cg_take_snapshot', { label: label }).then(function (res) {
					if (res && res.success) {
						window.alert('Snapshot lancé.');
						loadSnapshots();
					} else {
						window.alert('Erreur snapshot.');
					}
				});
			}

			document.getElementById('avi-cg-btn-refresh').addEventListener('click', function () {
				var btn = this; var orig = btn.textContent; btn.textContent = '23f3 Actualisation...'; btn.disabled = true;
				loadMetrics();
				loadSnapshots();
					setTimeout(function() { btn.textContent = '2713 Actualis00e9'; setTimeout(function() { btn.textContent = orig; btn.disabled = false; }, 1500); }, 1000);
				});
			document.getElementById('avi-cg-btn-snapshot').addEventListener('click', takeSnapshot);
			document.getElementById('avi-cg-btn-checksums').addEventListener('click', function () {
				post('avision_cg_update_checksums').then(function (res) {
					if (res && res.success) {
						window.alert('Référence mise à jour.');
						loadMetrics();
					} else {
						window.alert('Erreur mise à jour checksums.');
					}
				});
			});
			document.getElementById('avi-cg-btn-post-deploy').addEventListener('click', function () {
				var label = prompt('Label du déploiement :', 'post-deploy-' + new Date().toISOString().slice(0, 16).replace('T', '-').replace(':', ''));
				if (!label) return;
				var btn = this;
				var status = document.getElementById('avi-cg-deploy-status');
				btn.disabled = true;
				btn.textContent = '⏳ En cours...';
				status.textContent = '';
				jQuery.post(ajaxurl, {
					action: 'avision_cg_post_deploy',
					nonce: aviCgNonce,
					label: label
				}, function (res) {
					btn.disabled = false;
					btn.textContent = '✅ Valider déploiement';
					if (res.success && res.data.passed) {
						status.style.color = '#16a34a';
						status.textContent = '✅ Gate PASSED — checksums mis à jour — snapshot créé';
						loadMetrics();
						loadSnapshots();
					} else {
						status.style.color = '#dc2626';
						status.textContent = '❌ Gate FAILED — voir détails console';
						console.log('[AVI Gate]', res.data ? res.data.gate : res);
					}
				}).fail(function () {
					btn.disabled = false;
					btn.textContent = '✅ Valider déploiement';
					status.style.color = '#dc2626';
					status.textContent = '❌ Erreur AJAX';
				});
			});
			document.getElementById('avi-cg-modal-close').addEventListener('click', function () {
				var modal = document.getElementById('avi-cg-diff-modal');
				if (modal) {
					modal.classList.remove('is-open');
					modal.setAttribute('aria-hidden', 'true');
				}
			});

			if (document.readyState === 'loading') {
				document.addEventListener('DOMContentLoaded', function () {
					loadMetrics();
					loadSnapshots();
					setTimeout(function() { btn.textContent = '2713 Actualis00e9'; setTimeout(function() { btn.textContent = orig; btn.disabled = false; }, 1500); }, 1000);
				});
			} else {
				loadMetrics();
				loadSnapshots();
			}
			setInterval(loadMetrics, 60000);
		})();
		</script>
		<?php
	}
}
