<?php
/**
 * At-Risk Users report.
 *
 * Master-admin-only page that scores users on engagement (login recency,
 * content activity, tenure, + MemberPress subscription when present) and lists
 * those most at risk of going inactive — the early-warning complement to Ghost
 * Purge's after-the-fact cleanup.
 *
 * Scoring is done live but cheaply: a few grouped queries via HealthScorer,
 * then every user scored in PHP (capped for very large sites).
 *
 * @package Platform\Helm\Modules\Member_Health
 */

declare( strict_types = 1 );

namespace Platform\Helm\Modules\Member_Health;

use Platform\Helm\Core\Authorization;
use Platform\Helm\Core\Ui;

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

final class AtRiskUsersPage {

	public const PAGE_SLUG   = 'platform-helm-at-risk-users';
	public const PARENT_SLUG = 'platform-helm';

	private const SCORE_CAP = 2000; // Max users scored per page load.
	private const SHOW_ROWS = 100;  // Max rows rendered.

	public function register_hooks(): void {
		add_action( 'admin_menu', [ $this, 'register_menu' ], 52 );
	}

	public function register_menu(): void {
		if ( ! Authorization::is_master_admin() ) {
			return;
		}
		add_submenu_page(
			self::PARENT_SLUG,
			__( 'At-Risk Users', 'platform-helm' ),
			__( 'At-Risk Users', 'platform-helm' ),
			Authorization::ADMIN_CAP,
			self::PAGE_SLUG,
			[ $this, 'render' ]
		);
	}

	public function render(): void {
		Authorization::require_admin();

		// count_users() is expensive on large sites; cache it briefly.
		$total_users = get_transient( 'platform_helm_total_users' );
		if ( false === $total_users ) {
			$total_users = (int) count_users()['total_users'];
			set_transient( 'platform_helm_total_users', $total_users, 5 * MINUTE_IN_SECONDS );
		}
		$total_users = (int) $total_users;

		$users = get_users(
			[
				'number'  => self::SCORE_CAP,
				'orderby' => 'registered',
				'order'   => 'ASC',
			]
		);

		$scorer = new HealthScorer();
		$scorer->prime();

		$scored = [];
		$dist   = [ 'healthy' => 0, 'steady' => 0, 'at_risk' => 0, 'critical' => 0 ];
		foreach ( $users as $u ) {
			$s = $scorer->score( $u );
			$dist[ $s['risk'] ] = ( $dist[ $s['risk'] ] ?? 0 ) + 1;
			$scored[] = [ 'user' => $u, 's' => $s ];
		}
		usort( $scored, static fn( $a, $b ) => $a['s']['score'] <=> $b['s']['score'] );

		$at_risk_count = $dist['at_risk'] + $dist['critical'];

		Ui::open_page(
			__( 'At-Risk Users', 'platform-helm' ),
			__( 'Users most likely to disengage — by login recency, content activity and tenure. The early-warning side of Ghost Purge.', 'platform-helm' )
		);

		if ( count( $users ) >= self::SCORE_CAP && $total_users > self::SCORE_CAP ) {
			Ui::notice(
				esc_html( sprintf( /* translators: 1: scored, 2: total */ __( 'Large user base — scored the first %1$s of %2$s users.', 'platform-helm' ), number_format_i18n( self::SCORE_CAP ), number_format_i18n( $total_users ) ) ),
				'info'
			);
		}

		echo '<div class="stat-grid">';
		Ui::stat_tile( __( 'Users scored', 'platform-helm' ), number_format_i18n( count( $users ) ), 'blue', 'ti-users', sprintf( /* translators: %s: total */ __( 'of %s total', 'platform-helm' ), number_format_i18n( $total_users ) ), '' );
		Ui::stat_tile( __( 'Healthy', 'platform-helm' ), number_format_i18n( $dist['healthy'] ), 'green', 'ti-mood-smile', __( 'engaged', 'platform-helm' ), 'up' );
		Ui::stat_tile( __( 'At risk', 'platform-helm' ), number_format_i18n( $dist['at_risk'] ), 'amber', 'ti-alert-triangle', __( 'fading engagement', 'platform-helm' ), $dist['at_risk'] > 0 ? 'warn' : '', 'amber' );
		Ui::stat_tile( __( 'Critical', 'platform-helm' ), number_format_i18n( $dist['critical'] ), 'red', 'ti-mood-sad', __( 'likely inactive', 'platform-helm' ), $dist['critical'] > 0 ? 'danger' : '', $dist['critical'] > 0 ? 'red' : '' );
		echo '</div>';

		// Table — worst-scoring first.
		echo '<div class="card"><div class="card-header"><div class="card-title"><i class="ti ti-heartbeat" aria-hidden="true"></i> ' . esc_html__( 'Lowest engagement', 'platform-helm' );
		if ( $at_risk_count > 0 ) {
			echo ' <span class="badge badge-warning">' . esc_html( number_format_i18n( $at_risk_count ) ) . '</span>';
		}
		echo '</div><span class="text-faint">' . esc_html__( 'Most at-risk users first', 'platform-helm' ) . '</span></div>';

		if ( empty( $scored ) ) {
			echo '<div class="card-body"><p class="text-secondary">' . esc_html__( 'No users to score yet.', 'platform-helm' ) . '</p></div></div>';
			Ui::close_page();
			return;
		}

		echo '<div class="table-wrap"><table><thead><tr>';
		echo '<th>' . esc_html__( 'User', 'platform-helm' ) . '</th>';
		echo '<th>' . esc_html__( 'Health', 'platform-helm' ) . '</th>';
		echo '<th>' . esc_html__( 'Last login', 'platform-helm' ) . '</th>';
		echo '<th>' . esc_html__( 'Role', 'platform-helm' ) . '</th>';
		echo '<th class="right">' . esc_html__( 'Actions', 'platform-helm' ) . '</th>';
		echo '</tr></thead><tbody>';

		foreach ( array_slice( $scored, 0, self::SHOW_ROWS ) as $row ) {
			$u    = $row['user'];
			$s    = $row['s'];
			[ $label, $tone ] = HealthScorer::risk_meta( $s['risk'] );
			$login = $s['last_login'] > 0
				? sprintf( /* translators: %s: time diff */ __( '%s ago', 'platform-helm' ), human_time_diff( $s['last_login'], time() ) )
				: __( 'never / unknown', 'platform-helm' );
			$roles = ! empty( $u->roles ) ? implode( ', ', array_map( 'ucfirst', (array) $u->roles ) ) : '—';

			echo '<tr>';
			echo '<td><p class="url-cell">' . esc_html( (string) $u->display_name ) . '</p><p class="meta-line">' . esc_html( (string) $u->user_email ) . '</p></td>';
			echo '<td><span class="badge badge-' . esc_attr( $tone ) . '">' . esc_html( $label . ' · ' . $s['score'] ) . '</span></td>';
			echo '<td><span style="color:var(--text-muted)">' . esc_html( $login ) . '</span></td>';
			echo '<td>' . esc_html( $roles ) . '</td>';
			echo '<td class="right"><a class="btn btn-ghost btn-sm" href="' . esc_url( get_edit_user_link( (int) $u->ID ) ) . '">' . esc_html__( 'Profile', 'platform-helm' ) . '</a></td>';
			echo '</tr>';
		}
		echo '</tbody></table></div></div>';

		Ui::close_page();
	}
}
