<?php
/**
 * Usage dashboard AJAX and rendering helpers for SiteStaffr admin.
 *
 * @package    SiteStaffr
 * @subpackage SiteStaffr/admin/traits
 * @since      0.14.257
 */

if ( ! defined( 'WPINC' ) ) {
	die;
}

/**
 * Encapsulates Usage & Billing AJAX handlers and helper methods.
 */
// phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Plugin table names are internally derived from $wpdb->prefix and dynamic SQL fragments are trusted/internal.
trait SiteStaffr_Admin_Usage_Ajax_Trait {

	/**
	 * Handle Usage page channel filter AJAX requests.
	 *
	 * Returns rendered HTML fragments for conversation volume chart or recent conversations table.
	 *
	 * @since 0.14.27
	 * @return void
	 */
	public function ajax_filter_usage_data() {
		check_ajax_referer( 'sitestaffr_usage_filter_nonce', 'nonce' );

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => __( 'Unauthorized.', 'sitestaffr' ) ), 403 );
			return;
		}

		$section      = isset( $_POST['section'] ) ? sanitize_key( wp_unslash( $_POST['section'] ) ) : '';
		$channel      = isset( $_POST['channel'] ) ? sanitize_key( wp_unslash( $_POST['channel'] ) ) : 'all';
		$metric       = isset( $_POST['metric'] ) ? sanitize_key( wp_unslash( $_POST['metric'] ) ) : 'conversations';
		$cycle_offset = isset( $_POST['cycle_offset'] ) ? absint( wp_unslash( $_POST['cycle_offset'] ) ) : 0;

		if ( ! in_array( $section, array( 'chart', 'recent' ), true ) ) {
			wp_send_json_error( array( 'message' => __( 'Invalid section.', 'sitestaffr' ) ), 400 );
			return;
		}

		if ( ! in_array( $channel, array( 'all', 'widget', 'button' ), true ) ) {
			$channel = 'all';
		}

		if ( ! in_array( $metric, array( 'conversations', 'minutes' ), true ) ) {
			$metric = 'conversations';
		}

		// Minutes = the billed quantity; aggregate billed minutes per day.
		// Both fragments are constant literals — no request data reaches SQL.
		$agg_expr     = 'COUNT(*)';
		$metric_where = '';
		if ( 'minutes' === $metric ) {
			// Minutes are voice-only; exclude text chats so the Minutes chart
			// can't bundle text time (even when a channel pill's IN-list would
			// otherwise include chat_widget/chat_button).
			$agg_expr     = 'SUM(COALESCE(NULLIF(billable_minutes, 0), GREATEST(0, FLOOR(call_duration / 60))))';
			$metric_where = ' AND is_billable = 1 AND call_duration >= 30 AND ' . SiteStaffr_Call_Manager::voice_minutes_where_sql();
		}

		global $wpdb;
		$calls_table     = $wpdb->prefix . 'phoneease_calls';
		$period          = $this->get_usage_dashboard_period_window( $cycle_offset );
		$period_start_dt = $period['period_start_dt'];
		$period_end_dt   = $period['period_end_dt'];

		/*
		 * Dynamic table names are derived from trusted internals ($wpdb->prefix),
		 * while period bounds and channel values use placeholder-prepared args.
		 * Channel conditions are inlined per-variant to avoid concatenation.
		 */
		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber

		if ( 'chart' === $section ) {
			if ( 'button' === $channel ) {
				$volume_rows = $wpdb->get_results(
					$wpdb->prepare(
						"SELECT DATE(created_at) AS day_key, {$agg_expr} AS total
						 FROM {$calls_table}
						 WHERE created_at >= %s
						 AND created_at <= %s
						 AND (is_test_call = 0 OR is_test_call IS NULL)
						 AND minute_quota_exceeded = 0{$metric_where}
						 AND service_type IN (%s, %s)
						 GROUP BY DATE(created_at)
						 ORDER BY day_key ASC",
						$period_start_dt,
						$period_end_dt,
						'webrtc_button',
						'chat_button'
					)
				);
			} elseif ( 'widget' === $channel ) {
				$volume_rows = $wpdb->get_results(
					$wpdb->prepare(
						"SELECT DATE(created_at) AS day_key, {$agg_expr} AS total
						 FROM {$calls_table}
						 WHERE created_at >= %s
						 AND created_at <= %s
						 AND (is_test_call = 0 OR is_test_call IS NULL)
						 AND minute_quota_exceeded = 0{$metric_where}
						 AND (service_type IN (%s, %s, %s, %s, %s, %s) OR service_type IS NULL)
						 GROUP BY DATE(created_at)
						 ORDER BY day_key ASC",
						$period_start_dt,
						$period_end_dt,
						'webrtc_widget',
						'webrtc',
						'web',
						'widget',
						'',
						'chat_widget'
					)
				);
			} else {
				$volume_rows = $wpdb->get_results(
					$wpdb->prepare(
						"SELECT DATE(created_at) AS day_key, {$agg_expr} AS total
						 FROM {$calls_table}
						 WHERE created_at >= %s
						 AND created_at <= %s
						 AND (is_test_call = 0 OR is_test_call IS NULL)
						 AND minute_quota_exceeded = 0{$metric_where}
						 GROUP BY DATE(created_at)
						 ORDER BY day_key ASC",
						$period_start_dt,
						$period_end_dt
					)
				);
			}

			$daily_volume_map = array();
			if ( ! empty( $volume_rows ) ) {
				foreach ( $volume_rows as $row ) {
					$day_key = isset( $row->day_key ) ? (string) $row->day_key : '';
					if ( '' !== $day_key ) {
						$daily_volume_map[ $day_key ] = (int) $row->total;
					}
				}
			}

			$chart_labels    = array();
			$chart_values    = array();
			$chart_cursor    = strtotime( gmdate( 'Y-m-d', strtotime( $period_start_dt ) ) );
			$chart_cycle_end = strtotime( gmdate( 'Y-m-d', $period['cycle_end_ts'] ) );
			$chart_today_end = strtotime( gmdate( 'Y-m-d', strtotime( $period_end_dt ) ) );
			$chart_end       = min( $chart_cycle_end, $chart_today_end );

			while ( false !== $chart_cursor && false !== $chart_end && $chart_cursor <= $chart_end ) {
				$cursor_key     = gmdate( 'Y-m-d', $chart_cursor );
				$chart_labels[] = gmdate( 'M j', $chart_cursor );
				$chart_values[] = isset( $daily_volume_map[ $cursor_key ] ) ? (int) $daily_volume_map[ $cursor_key ] : 0;
				$chart_cursor   = strtotime( '+1 day', $chart_cursor );
			}

			wp_send_json_success(
				array(
					'section' => 'chart',
					'html'    => $this->render_usage_volume_chart_html( $chart_labels, $chart_values, $metric ),
				)
			);
		}

		if ( 'button' === $channel ) {
			$recent_calls = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT id, created_at, started_at, session_id, service_type, call_duration, billable_minutes
					 FROM {$calls_table}
					 WHERE created_at >= %s
					 AND created_at <= %s
					 AND minute_quota_exceeded = 0
					 AND (is_test_call = 0 OR is_test_call IS NULL)
					 AND purged_at IS NULL
					 AND service_type IN (%s, %s)
					 ORDER BY created_at DESC
					 LIMIT 10",
					$period_start_dt,
					$period_end_dt,
					'webrtc_button',
					'chat_button'
				)
			);
		} elseif ( 'widget' === $channel ) {
			$recent_calls = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT id, created_at, started_at, session_id, service_type, call_duration, billable_minutes
					 FROM {$calls_table}
					 WHERE created_at >= %s
					 AND created_at <= %s
					 AND minute_quota_exceeded = 0
					 AND (is_test_call = 0 OR is_test_call IS NULL)
					 AND purged_at IS NULL
					 AND (service_type IN (%s, %s, %s, %s, %s, %s) OR service_type IS NULL)
					 ORDER BY created_at DESC
					 LIMIT 10",
					$period_start_dt,
					$period_end_dt,
					'webrtc_widget',
					'webrtc',
					'web',
					'widget',
					'',
					'chat_widget'
				)
			);
		} else {
			$recent_calls = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT id, created_at, started_at, session_id, service_type, call_duration, billable_minutes
					 FROM {$calls_table}
					 WHERE created_at >= %s
					 AND created_at <= %s
					 AND minute_quota_exceeded = 0
					 AND (is_test_call = 0 OR is_test_call IS NULL)
					 AND purged_at IS NULL
					 ORDER BY created_at DESC
					 LIMIT 10",
					$period_start_dt,
					$period_end_dt
				)
			);
		}

		wp_send_json_success(
			array(
				'section' => 'recent',
				'html'    => $this->render_usage_recent_calls_html( $recent_calls ),
			)
		);
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
	}

	/**
	 * Resolve usage dashboard period window from billing cache.
	 *
	 * @since 0.14.27
	 * @param int $cycle_offset Optional cycle offset.
	 * @return array<string,mixed>
	 */
	private function get_usage_dashboard_period_window( $cycle_offset = 0 ) {
		$cycle_offset = absint( $cycle_offset );
		$billing_cache = array();
		if ( class_exists( 'SiteStaffr_Billing_State' ) ) {
			// Usage filters should use cached billing state; refresh happens on TTL or explicit manual action.
			SiteStaffr_Billing_State::maybe_refresh_cache( false );
			$billing_cache = SiteStaffr_Billing_State::get_cache();
		}

		$balance_option    = get_option( 'sitestaffr_minute_balance', array() );
		$period_start_raw  = '';
		if ( ! empty( $billing_cache['subscription_current_period_start'] ) ) {
			$period_start_raw = $billing_cache['subscription_current_period_start'];
		} elseif ( ! empty( $billing_cache['trial_started_at'] ) ) {
			$period_start_raw = $billing_cache['trial_started_at'];
		} elseif ( ! empty( $billing_cache['subscription_current_period_end'] ) && strtotime( $billing_cache['subscription_current_period_end'] ) ) {
			$period_start_raw = gmdate( 'Y-m-d H:i:s', strtotime( '-1 month', strtotime( $billing_cache['subscription_current_period_end'] ) ) );
		} elseif ( isset( $balance_option['last_reset_date'] ) ) {
			$period_start_raw = $balance_option['last_reset_date'] . ' 00:00:00';
		} else {
			$period_start_raw = current_time( 'Y-m-01 00:00:00' );
		}

		$base_period_start_ts = strtotime( $period_start_raw );
		if ( false === $base_period_start_ts ) {
			$base_period_start_ts = strtotime( current_time( 'Y-m-01 00:00:00' ) );
		}

		$status_key   = isset( $billing_cache['subscription_status'] ) ? sanitize_key( $billing_cache['subscription_status'] ) : 'active';
		$cycle_end_raw = '';
		if ( ! empty( $billing_cache['subscription_current_period_end'] ) ) {
			$cycle_end_raw = $billing_cache['subscription_current_period_end'];
		} elseif ( 'trial_active' === $status_key && ! empty( $billing_cache['trial_expires_at'] ) ) {
			$cycle_end_raw = $billing_cache['trial_expires_at'];
		}

		$base_cycle_end_ts = ! empty( $cycle_end_raw ) ? strtotime( $cycle_end_raw ) : false;
		if ( false === $base_cycle_end_ts || $base_cycle_end_ts < $base_period_start_ts ) {
			$base_cycle_end_ts = strtotime( current_time( 'mysql' ) );
		}

		$available_cycle_offsets = $this->get_usage_available_cycle_offsets( $base_period_start_ts );
		if ( ! in_array( $cycle_offset, $available_cycle_offsets, true ) ) {
			$cycle_offset = 0;
		}

		$selected_cycle_start_ts = strtotime( '-' . $cycle_offset . ' month', $base_period_start_ts );
		if ( false === $selected_cycle_start_ts ) {
			$selected_cycle_start_ts = $base_period_start_ts;
		}

		$selected_cycle_end_ts = strtotime( '-' . $cycle_offset . ' month', $base_cycle_end_ts );
		if ( false === $selected_cycle_end_ts || $selected_cycle_end_ts < $selected_cycle_start_ts ) {
			$selected_cycle_end_ts = $selected_cycle_start_ts;
		}

		$now_ts = strtotime( current_time( 'mysql' ) );
		if ( false === $now_ts ) {
			$now_ts = time();
		}

		$period_end_ts = ( 0 === $cycle_offset ) ? min( $selected_cycle_end_ts, $now_ts ) : $selected_cycle_end_ts;
		if ( $period_end_ts < $selected_cycle_start_ts ) {
			$period_end_ts = $selected_cycle_start_ts;
		}

		$period_start_dt = gmdate( 'Y-m-d H:i:s', $selected_cycle_start_ts );
		$period_end_dt   = gmdate( 'Y-m-d H:i:s', $period_end_ts );

		return array(
			'period_start_dt' => $period_start_dt,
			'period_end_dt'   => $period_end_dt,
			'cycle_end_ts'    => $selected_cycle_end_ts,
			'cycle_offset'    => $cycle_offset,
		);
	}

	/**
	 * Determine available billing cycle offsets from stored calls.
	 *
	 * @since 0.14.92
	 * @param int $base_period_start_ts Current cycle start timestamp.
	 * @return array<int,int>
	 */
	private function get_usage_available_cycle_offsets( $base_period_start_ts ) {
		$base_period_start_ts = absint( $base_period_start_ts );
		if ( empty( $base_period_start_ts ) ) {
			return array( 0 );
		}

		$available = array( 0 => 0 );
		global $wpdb;

		// Prefer true billing cycle windows from snapshots when available.
		$snapshot_table  = $wpdb->prefix . 'phoneease_billing_cycle_snapshots';
		$snapshot_exists = $wpdb->get_var(
			$wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $snapshot_table ) )
		);
		if ( $snapshot_exists === $snapshot_table ) {
			$snapshot_rows = $wpdb->get_results(
				$wpdb->prepare( 'SELECT DISTINCT period_start FROM %i ORDER BY period_start DESC LIMIT 60', $snapshot_table )
			);
			foreach ( $snapshot_rows as $snapshot_row ) {
				$period_start_raw = isset( $snapshot_row->period_start ) ? $snapshot_row->period_start : '';
				$period_start_ts  = ! empty( $period_start_raw ) ? strtotime( $period_start_raw ) : false;
				if ( false === $period_start_ts ) {
					continue;
				}
				$offset  = ( (int) gmdate( 'Y', $base_period_start_ts ) - (int) gmdate( 'Y', $period_start_ts ) ) * 12;
				$offset += (int) gmdate( 'n', $base_period_start_ts ) - (int) gmdate( 'n', $period_start_ts );
				if ( $offset < 0 ) {
					$offset = 0;
				}
				$available[ $offset ] = $offset;
			}
		}

			// Fallback/merge with call activity months.
			$calls_table = $wpdb->prefix . 'phoneease_calls';
			// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- trusted plugin calls table name.
			$cycle_rows  = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT MIN(created_at) AS first_call_at
	                 FROM {$calls_table}
	                 WHERE (is_test_call = 0 OR is_test_call IS NULL)
	                 AND created_at <= %s
	                 GROUP BY YEAR(created_at), MONTH(created_at)",
					current_time( 'mysql' )
				)
			);
			// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		foreach ( $cycle_rows as $cycle_row ) {
			$call_raw = isset( $cycle_row->first_call_at ) ? $cycle_row->first_call_at : '';
			$call_ts  = ! empty( $call_raw ) ? strtotime( $call_raw ) : false;
			if ( false === $call_ts ) {
				continue;
			}

			$offset  = ( (int) gmdate( 'Y', $base_period_start_ts ) - (int) gmdate( 'Y', $call_ts ) ) * 12;
			$offset += (int) gmdate( 'n', $base_period_start_ts ) - (int) gmdate( 'n', $call_ts );
			if ( $offset < 0 ) {
				$offset = 0;
			}
			$candidate_start = strtotime( '-' . $offset . ' month', $base_period_start_ts );
			if ( false !== $candidate_start && $candidate_start > $call_ts ) {
				$offset++;
			}
			$available[ $offset ] = $offset;
		}

		ksort( $available );
		return array_values( $available );
	}

	/**
	 * Render daily trend chart fragment HTML (conversations or billed minutes).
	 *
	 * @since 0.14.27
	 * @param array<int,string> $chart_labels Label list.
	 * @param array<int,int>    $chart_values Value list.
	 * @param string            $metric       'conversations' or 'minutes'.
	 * @return string
	 */
	private function render_usage_volume_chart_html( $chart_labels, $chart_values, $metric = 'conversations' ) {
		if ( empty( $chart_values ) ) {
			return '<p class="sitestaffr-chart-empty">' . esc_html__( 'No conversation data yet in this billing period.', 'sitestaffr' ) . '</p>';
		}

		$is_minutes  = ( 'minutes' === $metric );
		$chart_aria  = $is_minutes
			? __( 'Daily billed minutes chart', 'sitestaffr' )
			: __( 'Daily conversation volume chart', 'sitestaffr' );
		/* translators: %1$s: date label, %2$d: value for that day. */
		$tooltip_tpl = $is_minutes ? __( '%1$s: %2$d minutes', 'sitestaffr' ) : __( '%1$s: %2$d conversations', 'sitestaffr' );

		$chart_width       = 640;
		$chart_height      = 180;
		$chart_padding_x   = 18;
		$chart_padding_y   = 14;
		$chart_inner_width = $chart_width - ( $chart_padding_x * 2 );
		$chart_inner_height = $chart_height - ( $chart_padding_y * 2 );
		$chart_count        = count( $chart_values );
		$chart_step_x       = ( $chart_count > 1 ) ? ( $chart_inner_width / ( $chart_count - 1 ) ) : 0;
		$chart_max          = max( $chart_values );
		$chart_scale_max    = max( 10, (int) ( ceil( $chart_max / 10 ) * 10 ) );

		$chart_points       = array();
		$chart_point_markers = array();
		$chart_baseline_y    = $chart_padding_y + $chart_inner_height;
		for ( $i = 0; $i < $chart_count; $i++ ) {
			$x = $chart_padding_x + ( $chart_step_x * $i );
			$normalized = $chart_values[ $i ] / $chart_scale_max;
			$y = $chart_padding_y + ( $chart_inner_height - ( $normalized * $chart_inner_height ) );
			$chart_points[] = number_format( $x, 2, '.', '' ) . ',' . number_format( $y, 2, '.', '' );
			$chart_point_markers[] = array(
				'x'     => $x,
				'y'     => $y,
				'label' => $chart_labels[ $i ],
				'value' => (int) $chart_values[ $i ],
			);
		}

		$chart_points_attr = implode( ' ', $chart_points );
		$chart_area_points_attr = '';
		if ( ! empty( $chart_point_markers ) ) {
			$first_marker = $chart_point_markers[0];
			$last_marker  = $chart_point_markers[ $chart_count - 1 ];
			$area_points  = array(
				number_format( $first_marker['x'], 2, '.', '' ) . ',' . number_format( $chart_baseline_y, 2, '.', '' ),
			);
			foreach ( $chart_points as $chart_point ) {
				$area_points[] = $chart_point;
			}
			$area_points[] = number_format( $last_marker['x'], 2, '.', '' ) . ',' . number_format( $chart_baseline_y, 2, '.', '' );
			$chart_area_points_attr = implode( ' ', $area_points );
		}

		$chart_ticks = array();
		$tick_count  = 5;
		for ( $t = 0; $t < $tick_count; $t++ ) {
			$ratio = $t / ( $tick_count - 1 );
			$tick_value = (int) round( $chart_scale_max * ( 1 - $ratio ) );
			$tick_y = $chart_padding_y + ( $chart_inner_height * $ratio );
			$chart_ticks[] = array(
				'value' => $tick_value,
				'y'     => $tick_y,
			);
		}

		ob_start();
		?>
		<svg class="sitestaffr-chart-svg" viewBox="0 0 <?php echo esc_attr( $chart_width ); ?> <?php echo esc_attr( $chart_height ); ?>" role="img" aria-label="<?php echo esc_attr( $chart_aria ); ?>">
			<?php foreach ( $chart_ticks as $chart_tick ) : ?>
				<line x1="<?php echo esc_attr( $chart_padding_x ); ?>" y1="<?php echo esc_attr( number_format( $chart_tick['y'], 2, '.', '' ) ); ?>" x2="<?php echo esc_attr( $chart_width - $chart_padding_x ); ?>" y2="<?php echo esc_attr( number_format( $chart_tick['y'], 2, '.', '' ) ); ?>" stroke="#e5e7eb" stroke-width="1" />
				<text x="2" y="<?php echo esc_attr( number_format( $chart_tick['y'] + 4, 2, '.', '' ) ); ?>" font-size="10" fill="#6b7280"><?php echo esc_html( $chart_tick['value'] ); ?></text>
			<?php endforeach; ?>
			<?php if ( '' !== $chart_area_points_attr ) : ?>
				<polygon points="<?php echo esc_attr( $chart_area_points_attr ); ?>" fill="rgba(0, 131, 143, 0.12)" />
			<?php endif; ?>
			<polyline points="<?php echo esc_attr( $chart_points_attr ); ?>" fill="none" stroke="#00838F" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" />
			<?php foreach ( $chart_point_markers as $chart_marker ) : ?>
				<circle cx="<?php echo esc_attr( number_format( $chart_marker['x'], 2, '.', '' ) ); ?>" cy="<?php echo esc_attr( number_format( $chart_marker['y'], 2, '.', '' ) ); ?>" r="3.5" fill="#ffffff" stroke="#00838F" stroke-width="2">
					<title><?php echo esc_html( sprintf( $tooltip_tpl, $chart_marker['label'], $chart_marker['value'] ) ); ?></title>
				</circle>
				<?php if ( $chart_marker['value'] > 0 ) : ?>
				<text x="<?php echo esc_attr( number_format( $chart_marker['x'], 2, '.', '' ) ); ?>" y="<?php echo esc_attr( number_format( $chart_marker['y'] - 8, 2, '.', '' ) ); ?>" text-anchor="middle" font-size="10" font-weight="600" fill="#00838F" stroke="#ffffff" stroke-width="3" paint-order="stroke"><?php echo esc_html( $chart_marker['value'] ); ?></text>
				<?php endif; ?>
			<?php endforeach; ?>
		</svg>
		<div class="sitestaffr-chart-range">
			<span><?php echo esc_html( reset( $chart_labels ) ); ?></span>
			<span><?php echo esc_html( end( $chart_labels ) ); ?></span>
		</div>
		<?php
		return (string) ob_get_clean();
	}

	/**
	 * Render recent calls table fragment HTML.
	 *
	 * @since 0.14.27
	 * @param array<int,object> $recent_calls Recent call records.
	 * @return string
	 */
	private function render_usage_recent_calls_html( $recent_calls ) {
		if ( empty( $recent_calls ) ) {
			return '<div class="sitestaffr-empty-state"><p>' . esc_html__( 'No conversations yet in this billing period.', 'sitestaffr' ) . '</p></div>';
		}

		ob_start();
		?>
		<table class="wp-list-table widefat fixed striped sitestaffr-usage-recent-table">
			<thead>
				<tr>
					<th><?php esc_html_e( 'Session', 'sitestaffr' ); ?></th>
					<th><?php esc_html_e( 'Date', 'sitestaffr' ); ?></th>
					<th><?php esc_html_e( 'Channel', 'sitestaffr' ); ?></th>
					<th><?php esc_html_e( 'Duration', 'sitestaffr' ); ?></th>
				</tr>
			</thead>
			<tbody>
				<?php foreach ( $recent_calls as $call ) : ?>
				<tr>
					<td><?php
						$session_id_val = isset( $call->session_id ) ? (string) $call->session_id : '';
						if ( '' !== $session_id_val ) {
							$sid_detail_url = wp_nonce_url( add_query_arg(
								array(
									'page'    => 'sitestaffr',
									'action'  => 'view',
									'call_id' => (int) $call->id,
								),
								admin_url( 'admin.php' )
							), 'sitestaffr_view_call' );
							echo '<a href="' . esc_url( $sid_detail_url ) . '" class="sitestaffr-call-sid-link" title="' . esc_attr( $session_id_val ) . '">' . esc_html( substr( $session_id_val, 0, 12 ) ) . '&hellip;</a>';
						} else {
							echo '&mdash;';
						}
					?></td>
					<td><?php
						// Prefer created_at for billing-window consistency; fallback to started_at for legacy rows.
						$call_time_raw = ! empty( $call->created_at ) ? $call->created_at : $call->started_at;
						if ( is_numeric( $call_time_raw ) ) {
							$started_timestamp = (int) $call_time_raw;
						} else {
							// Stored via current_time('mysql') — site-local — so parse in the
							// site timezone (strtotime would read it as UTC).
							try {
								$started_timestamp = ( new DateTime( (string) $call_time_raw, wp_timezone() ) )->getTimestamp();
							} catch ( Exception $e ) {
								$started_timestamp = 0;
							}
						}
						echo esc_html( wp_date( 'M j', $started_timestamp, new DateTimeZone( 'America/New_York' ) ) );
					?></td>
					<td>
						<?php
						$channel_value     = isset( $call->service_type ) ? sanitize_key( $call->service_type ) : '';
						$session_id_value    = isset( $call->session_id ) ? (string) $call->session_id : '';
						$is_widget_session = ( 0 === strpos( $session_id_value, 'sess_' ) );
						if ( 'webrtc_button' === $channel_value ) {
							echo esc_html__( 'Button', 'sitestaffr' );
						} elseif ( 'webrtc_widget' === $channel_value ) {
							echo esc_html__( 'Widget', 'sitestaffr' );
						} elseif ( 'chat_button' === $channel_value ) {
							echo esc_html__( 'Chat Button', 'sitestaffr' );
						} elseif ( 'chat_widget' === $channel_value ) {
							echo esc_html__( 'Chat Widget', 'sitestaffr' );
						} elseif ( $is_widget_session || 'webrtc' === $channel_value || '' === $channel_value || 'web' === $channel_value || 'widget' === $channel_value ) {
							echo esc_html__( 'Widget', 'sitestaffr' );
						} elseif ( 'phone' === $channel_value ) {
							echo esc_html__( 'Phone', 'sitestaffr' );
						} else {
							echo esc_html( ucwords( str_replace( '_', ' ', $channel_value ) ) );
						}
						?>
					</td>
					<td>
						<?php
						if ( $call->call_duration > 0 ) {
							$minutes = floor( $call->call_duration / 60 );
							$seconds = $call->call_duration % 60;
							echo esc_html( sprintf( '%dm %02ds', $minutes, $seconds ) );
						} else {
							echo esc_html__( 'N/A', 'sitestaffr' );
						}
						?>
					</td>
				</tr>
				<?php endforeach; ?>
			</tbody>
		</table>
		<?php
		return (string) ob_get_clean();
	}
}
// phpcs:enable PluginCheck.Security.DirectDB.UnescapedDBParameter,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
