<?php
/**
 * Temporary Login admin notification emails (HTML, 2FA-style layout).
 *
 * @package TemporaryLogin
 */

namespace TemporaryLogin;

use WP_PowerSuite\Email\EmailComponents;
use WP_PowerSuite\Email\EmailDesign;
use WP_PowerSuite\Email\EmailRenderer;

/**
 * Sends queued HTML emails to the site admin on temporary-login events.
 */
class TemporaryLoginEmail {

	public const EVENT_CREATED = 'created';
	public const EVENT_REVOKED = 'revoked';
	public const EVENT_USED    = 'used';
	public const EVENT_UPDATED = 'updated';

	/** @var array<string, string> */
	private const PERM_LABELS = [
		'install_plugins' => 'Install plugins',
		'delete_plugins'  => 'Delete/deactivate plugins',
		'install_themes'  => 'Install themes',
		'delete_themes'   => 'Delete themes',
	];

	/**
	 * Queue an admin notification on shutdown (avoids blocking AJAX).
	 *
	 * @param string               $event   One of EVENT_* constants.
	 * @param object               $login   Login row object.
	 * @param array<string, mixed> $context Optional: actor_user_id, changes, client_ip, client_ua.
	 */
	public static function queue( string $event, object $login, array $context = [] ): void {
		$login_id = isset( $login->id ) ? (int) $login->id : 0;
		if ( $login_id <= 0 ) {
			return;
		}

		add_action(
			'shutdown',
			static function () use ( $event, $login_id, $context ): void {
				global $wpdb;
				$table = $wpdb->prefix . 'tl_temporary_logins';
				$row   = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$table} WHERE id = %d", $login_id ) );
				if ( ! $row ) {
					return;
				}
				self::send( $event, $row, $context );
			},
			20
		);
	}

	/**
	 * @param string               $event
	 * @param object               $login
	 * @param array<string, mixed> $context
	 */
	public static function send( string $event, object $login, array $context = [] ): void {
		$filter_map = [
			self::EVENT_CREATED => 'wpps_tl_notify_admin_on_created',
			self::EVENT_REVOKED => 'wpps_tl_notify_admin_on_revoked',
			self::EVENT_USED    => 'wpps_tl_notify_admin_on_login',
			self::EVENT_UPDATED => 'wpps_tl_notify_admin_on_updated',
		];

		$filter = $filter_map[ $event ] ?? 'wpps_tl_notify_admin_on_login';
		if ( ! apply_filters( $filter, true, $login, $event, $context ) ) {
			return;
		}

		$recipient = apply_filters( 'wpps_tl_notify_admin_email', get_option( 'admin_email' ), $login, $event, $context );
		if ( ! is_email( $recipient ) ) {
			return;
		}

		$ip_raw = isset( $context['client_ip'] ) && is_string( $context['client_ip'] ) && $context['client_ip'] !== ''
			? $context['client_ip']
			: ( isset( $_SERVER['REMOTE_ADDR'] ) ? wp_unslash( $_SERVER['REMOTE_ADDR'] ) : '' );
		$ip     = apply_filters(
			'wpps_tl_email_client_ip',
			is_string( $ip_raw ) && $ip_raw !== '' ? sanitize_text_field( $ip_raw ) : '',
			$login,
			$event
		);
		if ( $ip === '' ) {
			$ip = '—';
		}

		$ua_raw = isset( $context['client_ua'] ) && is_string( $context['client_ua'] )
			? $context['client_ua']
			: ( isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '' );

		$geo_rows = [];
		if ( apply_filters( 'wpps_tl_email_geo_lookup_enabled', false, $login, $ip, $event ) ) {
			$geo_rows = self::lookupGeoLinesForIp( $ip );
		}

		$device_summary = self::summarizeClientUserAgent( $ua_raw );
		if ( $device_summary === '' ) {
			$device_summary = $ua_raw !== '' ? self::truncateForEmailSummary( $ua_raw ) : '—';
		}

		$datetime   = wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) );
		$contact    = apply_filters( 'wpps_tl_email_contact_url', admin_url( 'admin.php?page=powersuite-temporary-login' ), $login, $event );
		$actor_id   = isset( $context['actor_user_id'] ) ? (int) $context['actor_user_id'] : 0;
		$changes    = isset( $context['changes'] ) && is_array( $context['changes'] ) ? $context['changes'] : [];

		if ( $actor_id <= 0 ) {
			if ( $event === self::EVENT_USED ) {
				$actor_id = (int) $login->user_id;
			} elseif ( $event === self::EVENT_REVOKED && ! empty( $login->revoked_by ) ) {
				$actor_id = (int) $login->revoked_by;
			} else {
				$actor_id = get_current_user_id();
			}
		}

		$copy      = self::getEventCopy( $event, $login, $actor_id, $changes );
		$meta_rows = self::buildLoginMetaRows( $login );
		$actor     = get_userdata( $actor_id );
		$performed_label = $event === self::EVENT_USED
			? __( 'Signed in as', 'wp-powersuite' )
			: __( 'Performed by', 'wp-powersuite' );
		array_unshift(
			$meta_rows,
			[
				'label' => $performed_label,
				'value' => self::formatUserLabel( $actor ),
			]
		);
		$html      = self::buildHtmlEmail(
			$copy['subject_line'],
			$copy['intro'],
			$copy['highlight_label'],
			$copy['highlight_text'],
			$copy['notice_html'],
			$meta_rows,
			$changes,
			$ip,
			$geo_rows,
			$device_summary,
			$ua_raw,
			$datetime,
			$contact
		);

		$site_name = wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
		if ( $site_name === '' ) {
			$site_name = 'WordPress';
		}

		$subject = sprintf( '[%s] %s', $site_name, $copy['email_subject'] );
		$headers = EmailRenderer::headers();

		wp_mail( $recipient, wp_specialchars_decode( $subject ), $html, $headers );
	}

	/**
	 * Build human-readable change lines from an update payload.
	 *
	 * @param object               $login       Row before update.
	 * @param array<string, mixed> $update_data Keys written to the database.
	 * @return array<int, array{label: string, from: string, to: string}>
	 */
	public static function buildUpdateChanges( object $login, array $update_data ): array {
		$changes = [];

		if ( isset( $update_data['role'] ) ) {
			$changes[] = [
				'label' => __( 'Role', 'wp-powersuite' ),
				'from'  => self::formatRoleLabel( (string) $login->role ),
				'to'    => self::formatRoleLabel( (string) $update_data['role'] ),
			];
		}

		if ( isset( $update_data['expires_at'] ) ) {
			$changes[] = [
				'label' => __( 'Expires', 'wp-powersuite' ),
				'from'  => self::formatExpiresAt( (string) $login->expires_at ),
				'to'    => self::formatExpiresAt( (string) $update_data['expires_at'] ),
			];
		}

		if ( isset( $update_data['single_use'] ) ) {
			$changes[] = [
				'label' => __( 'Single use', 'wp-powersuite' ),
				'from'  => ! empty( $login->single_use ) ? __( 'Yes', 'wp-powersuite' ) : __( 'No', 'wp-powersuite' ),
				'to'    => ! empty( $update_data['single_use'] ) ? __( 'Yes', 'wp-powersuite' ) : __( 'No', 'wp-powersuite' ),
			];
		}

		if ( isset( $update_data['max_uses'] ) ) {
			$changes[] = [
				'label' => __( 'Max uses', 'wp-powersuite' ),
				'from'  => self::formatMaxUses( (int) $login->max_uses ),
				'to'    => self::formatMaxUses( (int) $update_data['max_uses'] ),
			];
		}

		if ( array_key_exists( 'ip_restriction', $update_data ) ) {
			$changes[] = [
				'label' => __( 'IP restriction', 'wp-powersuite' ),
				'from'  => self::formatOptionalText( $login->ip_restriction ?? null ),
				'to'    => self::formatOptionalText( $update_data['ip_restriction'] ),
			];
		}

		if ( array_key_exists( 'redirect_to', $update_data ) ) {
			$changes[] = [
				'label' => __( 'Redirect URL', 'wp-powersuite' ),
				'from'  => self::formatOptionalText( $login->redirect_to ?? null ),
				'to'    => self::formatOptionalText( $update_data['redirect_to'] ),
			];
		}

		if ( isset( $update_data['allowed_capabilities'] ) ) {
			$changes[] = [
				'label' => __( 'Plugin/theme permissions', 'wp-powersuite' ),
				'from'  => self::formatPermissionsJson( (string) ( $login->allowed_capabilities ?? '' ) ),
				'to'    => self::formatPermissionsJson( (string) $update_data['allowed_capabilities'] ),
			];
		}

		return $changes;
	}

	/**
	 * @param string               $event
	 * @param object               $login
	 * @param int                  $actor_user_id
	 * @param array<int, mixed>    $changes
	 * @return array{email_subject: string, subject_line: string, intro: string, highlight_label: string, highlight_text: string, notice_html: string}
	 */
	private static function getEventCopy( string $event, object $login, int $actor_user_id, array $changes ): array {
		$login_id   = (int) $login->id;
		$manage_url = esc_url( admin_url( 'admin.php?page=powersuite-temporary-login' ) );

		switch ( $event ) {
			case self::EVENT_CREATED:
				return [
					'email_subject'   => __( 'Temporary login link created', 'wp-powersuite' ),
					'subject_line'    => __( 'Temporary login link created', 'wp-powersuite' ),
					'intro'           => __( 'A new temporary login link was created on your site.', 'wp-powersuite' ),
					'highlight_label' => __( 'Event', 'wp-powersuite' ),
					'highlight_text'  => sprintf(
						/* translators: %d: login row ID */
						__( 'Link #%d created', 'wp-powersuite' ),
						$login_id
					),
					'notice_html'     => '<p class="notice-text">' . esc_html__( 'Share the login URL only with the intended recipient. You can revoke this link anytime from WP PowerSuite → Temporary Login.', 'wp-powersuite' ) . '</p>',
				];

			case self::EVENT_REVOKED:
				return [
					'email_subject'   => __( 'Temporary login link revoked', 'wp-powersuite' ),
					'subject_line'    => __( 'Temporary login link revoked', 'wp-powersuite' ),
					'intro'           => __( 'A temporary login link was revoked and can no longer be used.', 'wp-powersuite' ),
					'highlight_label' => __( 'Event', 'wp-powersuite' ),
					'highlight_text'  => sprintf(
						/* translators: %d: login row ID */
						__( 'Link #%d revoked', 'wp-powersuite' ),
						$login_id
					),
					'notice_html'     => '<p class="notice-text"><strong>' . esc_html__( 'Sessions ended.', 'wp-powersuite' ) . '</strong> ' . esc_html__( 'Any active sessions for this link were terminated.', 'wp-powersuite' ) . '</p>',
				];

			case self::EVENT_UPDATED:
				return [
					'email_subject'   => __( 'Temporary login link updated', 'wp-powersuite' ),
					'subject_line'    => __( 'Temporary login link updated', 'wp-powersuite' ),
					'intro'           => __( 'Settings for an existing temporary login link were changed.', 'wp-powersuite' ),
					'highlight_label' => __( 'Event', 'wp-powersuite' ),
					'highlight_text'  => sprintf(
						/* translators: %d: login row ID */
						__( 'Link #%d updated', 'wp-powersuite' ),
						$login_id
					),
					'notice_html'     => '<p class="notice-text">' . esc_html__( 'If you did not make this change, review active links and revoke any you do not recognize.', 'wp-powersuite' ) . ' <a href="' . $manage_url . '" class="link">' . esc_html__( 'Manage temporary logins', 'wp-powersuite' ) . '</a></p>',
				];

			case self::EVENT_USED:
			default:
				return [
					'email_subject'   => __( 'Temporary login link used', 'wp-powersuite' ),
					'subject_line'    => __( 'Temporary login link used', 'wp-powersuite' ),
					'intro'           => __( 'Someone signed in using a temporary login link.', 'wp-powersuite' ),
					'highlight_label' => __( 'Event', 'wp-powersuite' ),
					'highlight_text'  => sprintf(
						/* translators: %d: login row ID */
						__( 'Link #%d used', 'wp-powersuite' ),
						$login_id
					),
					'notice_html'     => '<p class="notice-text"><strong>' . esc_html__( 'Not authorized?', 'wp-powersuite' ) . '</strong> ' . esc_html__( 'Revoke the link immediately under WP PowerSuite → Temporary Login.', 'wp-powersuite' ) . ' <a href="' . $manage_url . '" class="link">' . esc_html__( 'Manage temporary logins', 'wp-powersuite' ) . '</a></p>',
				];
		}
	}

	/**
	 * @return array<int, array{label: string, value: string}>
	 */
	private static function buildLoginMetaRows( object $login ): array {
		$target = get_userdata( (int) $login->user_id );
		$creator = get_userdata( (int) $login->created_by );

		$rows = [
			[
				'label' => __( 'Login ID', 'wp-powersuite' ),
				'value' => (string) (int) $login->id,
			],
			[
				'label' => __( 'Target user', 'wp-powersuite' ),
				'value' => self::formatUserLabel( $target ),
			],
			[
				'label' => __( 'Target email', 'wp-powersuite' ),
				'value' => $target ? (string) $target->user_email : '—',
			],
			[
				'label' => __( 'Session role', 'wp-powersuite' ),
				'value' => self::formatRoleLabel( (string) $login->role ),
			],
			[
				'label' => __( 'Expires', 'wp-powersuite' ),
				'value' => self::formatExpiresAt( (string) $login->expires_at ),
			],
			[
				'label' => __( 'Single use', 'wp-powersuite' ),
				'value' => ! empty( $login->single_use ) ? __( 'Yes', 'wp-powersuite' ) : __( 'No', 'wp-powersuite' ),
			],
			[
				'label' => __( 'Max uses', 'wp-powersuite' ),
				'value' => self::formatMaxUses( (int) $login->max_uses ),
			],
			[
				'label' => __( 'Times used', 'wp-powersuite' ),
				'value' => (string) (int) ( $login->used_count ?? 0 ),
			],
			[
				'label' => __( 'IP restriction', 'wp-powersuite' ),
				'value' => self::formatOptionalText( $login->ip_restriction ?? null ),
			],
			[
				'label' => __( 'Redirect URL', 'wp-powersuite' ),
				'value' => self::formatOptionalText( $login->redirect_to ?? null ),
			],
			[
				'label' => __( 'Plugin/theme permissions', 'wp-powersuite' ),
				'value' => self::formatPermissionsJson( (string) ( $login->allowed_capabilities ?? '' ) ),
			],
			[
				'label' => __( 'User-Agent binding', 'wp-powersuite' ),
				'value' => ! empty( $login->bind_user_agent ) ? __( 'Enabled', 'wp-powersuite' ) : __( 'Disabled', 'wp-powersuite' ),
			],
			[
				'label' => __( 'Link created by', 'wp-powersuite' ),
				'value' => self::formatUserLabel( $creator ),
			],
			[
				'label' => __( 'Created at', 'wp-powersuite' ),
				'value' => self::formatMysqlDatetime( (string) ( $login->created_at ?? '' ) ),
			],
		];

		if ( ! empty( $login->is_revoked ) ) {
			$revoker = ! empty( $login->revoked_by ) ? get_userdata( (int) $login->revoked_by ) : null;
			$rows[]  = [
				'label' => __( 'Revoked by', 'wp-powersuite' ),
				'value' => self::formatUserLabel( $revoker ),
			];
			$rows[]  = [
				'label' => __( 'Revoked at', 'wp-powersuite' ),
				'value' => self::formatMysqlDatetime( (string) ( $login->revoked_at ?? '' ) ),
			];
		}

		return $rows;
	}

	private static function formatUserLabel( ?\WP_User $user ): string {
		if ( ! $user instanceof \WP_User ) {
			return __( 'Unknown', 'wp-powersuite' );
		}
		return sprintf(
			'%s (%s)',
			$user->display_name,
			$user->user_login
		);
	}

	private static function formatRoleLabel( string $role ): string {
		if ( $role === '' ) {
			return '—';
		}
		$roles = wp_roles();
		if ( isset( $roles->role_names[ $role ] ) ) {
			return (string) $roles->role_names[ $role ] . ' (' . $role . ')';
		}
		return $role;
	}

	private static function formatExpiresAt( string $expires_at ): string {
		if ( $expires_at === '' || $expires_at === '0000-00-00 00:00:00' ) {
			return __( 'Never', 'wp-powersuite' );
		}
		return self::formatMysqlDatetime( $expires_at );
	}

	private static function formatMysqlDatetime( string $mysql ): string {
		if ( $mysql === '' || $mysql === '0000-00-00 00:00:00' ) {
			return '—';
		}
		$ts = mysql2date( 'U', $mysql, false );
		if ( ! $ts ) {
			return $mysql;
		}
		return wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), (int) $ts );
	}

	private static function formatMaxUses( int $max_uses ): string {
		return $max_uses > 0 ? (string) $max_uses : __( 'Unlimited', 'wp-powersuite' );
	}

	private static function formatOptionalText( mixed $value ): string {
		if ( $value === null || $value === '' ) {
			return __( 'None', 'wp-powersuite' );
		}
		return (string) $value;
	}

	private static function formatPermissionsJson( string $json ): string {
		$defaults = [
			'install_plugins' => false,
			'delete_plugins'  => false,
			'install_themes'  => false,
			'delete_themes'   => false,
		];
		$decoded = json_decode( $json, true );
		if ( is_array( $decoded ) ) {
			foreach ( array_keys( $defaults ) as $key ) {
				if ( array_key_exists( $key, $decoded ) ) {
					$defaults[ $key ] = (bool) $decoded[ $key ];
				}
			}
		}
		$enabled = [];
		foreach ( self::PERM_LABELS as $key => $label ) {
			if ( ! empty( $defaults[ $key ] ) ) {
				$enabled[] = $label;
			}
		}
		if ( empty( $enabled ) ) {
			return __( 'None (default restrictions)', 'wp-powersuite' );
		}
		return implode( ', ', $enabled );
	}

	/**
	 * @param array<int, array{label: string, value: string}>     $meta_rows
	 * @param array<int, array{label: string, from?: string, to?: string, value?: string}> $changes
	 * @param array<int, array{label: string, value: string}>   $geo_rows
	 */
	private static function buildHtmlEmail(
		string $subject_line,
		string $intro,
		string $highlight_label,
		string $highlight_text,
		string $notice_html,
		array $meta_rows,
		array $changes,
		string $ip,
		array $geo_rows,
		string $device_summary,
		string $ua_raw,
		string $datetime,
		string $contact_url
	): string {
		$content  = EmailComponents::paragraph( esc_html( $intro ) );
		$content .= EmailComponents::highlightBlock( $highlight_label, $highlight_text );

		$meta_detail_rows = [];
		foreach ( $meta_rows as $row ) {
			if ( empty( $row['label'] ) || ! isset( $row['value'] ) || (string) $row['value'] === '' ) {
				continue;
			}
			$meta_detail_rows[] = [
				'label' => (string) $row['label'],
				'value' => (string) $row['value'],
			];
		}
		$content .= EmailComponents::detailsBox( __( 'Temporary login details', 'wp-powersuite' ), $meta_detail_rows );

		if ( ! empty( $changes ) ) {
			$change_rows = [];
			foreach ( $changes as $change ) {
				$label = isset( $change['label'] ) ? (string) $change['label'] : '';
				if ( $label === '' ) {
					continue;
				}
				if ( isset( $change['value'] ) ) {
					$change_rows[] = [ 'label' => $label, 'value' => (string) $change['value'] ];
					continue;
				}
				$from = isset( $change['from'] ) ? (string) $change['from'] : '—';
				$to   = isset( $change['to'] ) ? (string) $change['to'] : '—';
				$change_rows[] = [
					'label' => $label,
					'value' => $from . ' → ' . $to,
				];
			}
			if ( $change_rows !== [] ) {
				$content .= EmailComponents::detailsBox( __( 'Changes applied', 'wp-powersuite' ), $change_rows );
			}
		}

		$request_rows = [
			[ 'label' => __( 'Time', 'wp-powersuite' ), 'value' => $datetime ],
			[ 'label' => __( 'IP address', 'wp-powersuite' ), 'value' => $ip ],
			[ 'label' => __( 'Device', 'wp-powersuite' ), 'value' => $device_summary ],
		];
		foreach ( $geo_rows as $row ) {
			if ( empty( $row['label'] ) || empty( $row['value'] ) ) {
				continue;
			}
			$request_rows[] = [
				'label' => (string) $row['label'],
				'value' => (string) $row['value'],
			];
		}
		$content .= EmailComponents::detailsBox( __( 'Request details', 'wp-powersuite' ), $request_rows );

		if ( $ua_raw !== '' ) {
			$content .= EmailComponents::detailRow(
				__( 'User-Agent', 'wp-powersuite' ),
				self::truncateForEmailSummary( $ua_raw, 2048 ),
				true
			);
		}

		$content .= EmailComponents::notice( $notice_html, 'warning' );
		$content .= EmailComponents::divider();
		$content .= EmailComponents::paragraph(
			esc_html__( 'This is an automated security notification from your site. If you did not expect this message, review temporary logins and revoke any suspicious links.', 'wp-powersuite' ),
			[ 'size' => EmailDesign::FONT_SMALL, 'muted' => true, 'margin' => '0' ]
		);

		$footer = sprintf(
			'%s <a href="%s" style="color:%s;">%s</a>',
			esc_html__( 'Manage links:', 'wp-powersuite' ),
			esc_url( $contact_url ),
			esc_attr( EmailRenderer::accentColor() ),
			esc_html__( 'Temporary Login', 'wp-powersuite' )
		);

		return EmailRenderer::wrap(
			[
				'title'        => $subject_line,
				'content'      => $content,
				'footer'       => $footer,
				'footer_brand' => esc_html__( 'This is an automated message from your site security system.', 'wp-powersuite' ),
			]
		);
	}

	/**
	 * @return array<int, array{label: string, value: string}>
	 */
	private static function lookupGeoLinesForIp( string $ip ): array {
		if ( $ip === '' || $ip === '—' ) {
			return [];
		}
		if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
			return [];
		}

		if ( ! filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
			return [
				[
					'label' => __( 'Estimated location:', 'wp-powersuite' ),
					'value' => __( 'Unavailable on private or reserved IP addresses', 'wp-powersuite' ),
				],
			];
		}

		$cache_key = 'wpps_tl_ge_' . md5( $ip );
		$cached    = get_transient( $cache_key );
		if ( is_array( $cached ) ) {
			return $cached;
		}

		$url = apply_filters(
			'wpps_tl_geo_lookup_url',
			'https://ipwho.is/' . rawurlencode( $ip ) . '?fields=success,message,country,region,city,type,connection',
			$ip
		);

		$response = wp_remote_get(
			$url,
			[
				'timeout'     => (int) apply_filters( 'wpps_tl_geo_http_timeout', 3, $ip ),
				'redirection' => 1,
				'user-agent'  => 'WP-PowerSuite/TemporaryLogin',
			]
		);

		$lines = [];
		if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
			set_transient( $cache_key, $lines, (int) apply_filters( 'wpps_tl_geo_cache_ttl_empty', MINUTE_IN_SECONDS * 15, $ip ) );
			return $lines;
		}

		$data = json_decode( (string) wp_remote_retrieve_body( $response ), true );
		if ( ! is_array( $data ) || empty( $data['success'] ) ) {
			set_transient( $cache_key, $lines, (int) apply_filters( 'wpps_tl_geo_cache_ttl_empty', MINUTE_IN_SECONDS * 15, $ip ) );
			return $lines;
		}

		if ( ! empty( $data['city'] ) || ! empty( $data['region'] ) || ! empty( $data['country'] ) ) {
			$loc_parts = array_filter(
				[
					(string) ( $data['city'] ?? '' ),
					(string) ( $data['region'] ?? '' ),
					(string) ( $data['country'] ?? '' ),
				]
			);
			if ( ! empty( $loc_parts ) ) {
				$lines[] = [
					'label' => __( 'Approximate location:', 'wp-powersuite' ),
					'value' => implode( ', ', $loc_parts ),
				];
			}
		}

		$conn = isset( $data['connection'] ) && is_array( $data['connection'] ) ? $data['connection'] : [];
		if ( ! empty( $conn['isp'] ) || ! empty( $conn['org'] ) ) {
			$net = implode(
				' · ',
				array_unique(
					array_filter(
						[
							(string) ( $conn['isp'] ?? '' ),
							(string) ( $conn['org'] ?? '' ),
						]
					)
				)
			);
			if ( $net !== '' ) {
				$lines[] = [
					'label' => __( 'Network:', 'wp-powersuite' ),
					'value' => $net,
				];
			}
		}

		if ( ! empty( $data['type'] ) ) {
			$lines[] = [
				'label' => __( 'Connection type:', 'wp-powersuite' ),
				'value' => (string) $data['type'],
			];
		}

		$ttl_ok = (int) apply_filters( 'wpps_tl_geo_cache_ttl', HOUR_IN_SECONDS, $ip );
		set_transient( $cache_key, $lines, $ttl_ok > 60 ? $ttl_ok : HOUR_IN_SECONDS );

		return $lines;
	}

	private static function summarizeClientUserAgent( string $ua ): string {
		$ua = trim( preg_replace( '/[\r\n\x00]/', '', $ua ) );
		if ( $ua === '' ) {
			return '';
		}

		$browser = self::guessBrowserLabel( $ua );
		$os      = self::guessOperatingSystemLabel( $ua );

		if ( $browser !== '' && $os !== '' ) {
			return sprintf( __( '%1$s on %2$s', 'wp-powersuite' ), $browser, $os );
		}

		return $browser !== '' ? $browser : $os;
	}

	private static function truncateForEmailSummary( string $text, int $length = 200 ): string {
		if ( function_exists( 'mb_strlen' ) && function_exists( 'mb_substr' ) ) {
			if ( mb_strlen( $text ) > $length ) {
				return mb_substr( $text, 0, $length ) . '…';
			}
		} elseif ( strlen( $text ) > $length ) {
			return substr( $text, 0, $length ) . '…';
		}
		return $text;
	}

	private static function guessBrowserLabel( string $ua ): string {
		if ( preg_match( '/SamsungBrowser\/([\d.]+)/i', $ua, $m ) ) {
			return 'Samsung Internet ' . $m[1];
		}
		if ( preg_match( '/Edg(?:e|)\/([\d.]+)/i', $ua, $m ) ) {
			return 'Microsoft Edge ' . $m[1];
		}
		if ( preg_match( '/OPR\/([\d.]+)/i', $ua, $m ) || preg_match( '/Opera\/([\d.]+)/i', $ua, $m ) ) {
			return 'Opera ' . $m[1];
		}
		if ( preg_match( '/CriOS\/([\d.]+)/i', $ua, $m ) ) {
			return 'Chrome (iOS) ' . $m[1];
		}
		if ( preg_match( '/FxiOS\/([\d.]+)/i', $ua, $m ) ) {
			return 'Firefox (iOS) ' . $m[1];
		}
		if ( preg_match( '/Firefox\/([\d.]+)/i', $ua, $m ) ) {
			return 'Firefox ' . $m[1];
		}
		if ( preg_match( '/Chrome\/([\d.]+)/i', $ua, $m ) && ! preg_match( '/Edg/i', $ua ) ) {
			return 'Chrome ' . $m[1];
		}
		if ( preg_match( '/Version\/([\d.]+).*Safari/i', $ua, $m ) && ! preg_match( '/Chrome|Chromium|CriOS/i', $ua ) ) {
			return 'Safari ' . $m[1];
		}

		return '';
	}

	private static function guessOperatingSystemLabel( string $ua ): string {
		if ( preg_match( '/Windows NT ([\d.]+)/i', $ua, $m ) ) {
			return 'Windows ' . $m[1];
		}
		if ( preg_match( '/Windows Phone ([\d.]+)/i', $ua, $m ) ) {
			return 'Windows Phone ' . $m[1];
		}
		if ( stripos( $ua, 'Windows' ) !== false ) {
			return 'Windows';
		}
		if ( preg_match( '/Mac OS X [\d_.]+/i', $ua ) ) {
			return 'macOS';
		}
		if ( stripos( $ua, 'iPhone' ) !== false ) {
			return __( 'iPhone (iOS)', 'wp-powersuite' );
		}
		if ( stripos( $ua, 'iPad' ) !== false ) {
			return __( 'iPad', 'wp-powersuite' );
		}
		if ( preg_match( '/Android ([\d.]+)/i', $ua, $m ) ) {
			return 'Android ' . $m[1];
		}
		if ( preg_match( '/CrOS .*Chrome\/([\d.]+)/i', $ua, $m ) ) {
			return 'Chrome OS';
		}
		if ( stripos( $ua, 'Linux' ) !== false ) {
			return 'Linux';
		}

		return '';
	}
}
