<?php
defined( 'ABSPATH' ) || exit;

/**
 * Admin surfaces for cross-domain handoff: the logged-in journey viewer, and the
 * purge action.
 *
 * The viewer is Mode B's whole point — the destination stores only an opaque
 * token, and a marketer follows it back here to read the journey. No visitor data
 * ever crosses origins in that mode.
 *
 * Everything rendered here comes from URL parameters a stranger controlled — UTM
 * values, referrer hostnames, page paths. It is all escaped on output. This is the
 * single most important detail in the file.
 */
class BW_Lead_AI_Handoff_Admin {

	const PAGE_SLUG   = 'bw-lead-ai-journey';
	const PURGE_ACTION = 'bw_lead_ai_handoff_purge';

	private static $instance = null;

	public static function instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	public function register() {
		add_action( 'admin_menu', array( $this, 'menu' ), 20 );
		add_action( 'admin_post_' . self::PURGE_ACTION, array( $this, 'handle_purge' ) );
	}

	/**
	 * Capability required to read a stored journey. Defaults to manage_options;
	 * filterable so a site can let its marketing role in without making them admins.
	 */
	public static function view_capability() {
		return apply_filters( 'bw_lead_ai_view_journey_capability', 'manage_options' );
	}

	public static function viewer_url( $token = '' ) {
		$url = admin_url( 'options-general.php?page=' . self::PAGE_SLUG );
		return $token ? add_query_arg( 'token', rawurlencode( $token ), $url ) : $url;
	}

	public function menu() {
		add_submenu_page(
			'options-general.php',
			__( 'BW Lead AI — Visitor journey', 'bw-lead-ai' ),
			__( 'BW Lead AI — Visitor journey', 'bw-lead-ai' ),
			self::view_capability(),
			self::PAGE_SLUG,
			array( $this, 'render_viewer' )
		);
		// Registered but kept out of the menu: it is only ever reached by following
		// a token link, never by browsing.
		remove_submenu_page( 'options-general.php', self::PAGE_SLUG );
	}

	public function handle_purge() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'You do not have permission to do that.', 'bw-lead-ai' ) );
		}
		check_admin_referer( self::PURGE_ACTION );

		$deleted = BW_Lead_AI_Handoff_Store::purge_all();

		wp_safe_redirect(
			add_query_arg(
				array(
					'page'      => 'bw-lead-ai',
					'tab'       => 'handoff',
					'bw_purged' => (int) $deleted,
				),
				admin_url( 'options-general.php' )
			)
		);
		exit;
	}

	public function render_viewer() {
		if ( ! current_user_can( self::view_capability() ) ) {
			wp_die( esc_html__( 'You do not have permission to view this.', 'bw-lead-ai' ) );
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only lookup by opaque token.
		$token  = isset( $_GET['token'] ) ? sanitize_text_field( wp_unslash( $_GET['token'] ) ) : '';
		$record = BW_Lead_AI_Handoff_Store::get_record( $token );

		echo '<div class="wrap bw-lead-ai">';
		echo '<h1>' . esc_html__( 'Visitor journey', 'bw-lead-ai' ) . '</h1>';

		if ( ! $record ) {
			echo '<div class="notice notice-warning"><p>';
			echo esc_html__( 'No journey found for that link. It may have expired, or the visitor never completed the handoff.', 'bw-lead-ai' );
			echo '</p></div>';
			echo '<p class="description">';
			echo esc_html__( 'Links are only kept once the destination confirms a submission. An unconfirmed link expires within the token lifetime set on the Handoff tab.', 'bw-lead-ai' );
			echo '</p></div>';
			return;
		}

		$this->render_meta( $record );
		$this->render_payload( $record['payload'] );

		echo '</div>';
	}

	private function render_meta( $record ) {
		$rows = array(
			__( 'Status', 'bw-lead-ai' )    => ( BW_Lead_AI_Handoff_Store::STATUS_CONFIRMED === $record['status'] )
				? __( 'Confirmed — a submission was reported by the destination', 'bw-lead-ai' )
				: __( 'Pending — no submission reported yet', 'bw-lead-ai' ),
			__( 'Captured', 'bw-lead-ai' )  => $record['created_at'],
			__( 'Confirmed', 'bw-lead-ai' ) => $record['confirmed_at'] ? $record['confirmed_at'] : '—',
			__( 'Kept until', 'bw-lead-ai' ) => ( BW_Lead_AI_Handoff_Store::NEVER === $record['expires_at'] )
				? __( 'No expiry set', 'bw-lead-ai' )
				: $record['expires_at'],
		);
		if ( ! empty( $record['context'] ) ) {
			$rows[ __( 'Reported by', 'bw-lead-ai' ) ] = $record['context'];
		}
		if ( ! empty( $record['data_claimed_at'] ) ) {
			$rows[ __( 'Data claimed', 'bw-lead-ai' ) ] = $record['data_claimed_at'];
		}

		echo '<table class="widefat striped" style="max-width:760px;margin-bottom:24px;"><tbody>';
		foreach ( $rows as $label => $value ) {
			echo '<tr><th scope="row" style="width:180px;">' . esc_html( $label ) . '</th>';
			echo '<td>' . esc_html( $value ) . '</td></tr>';
		}
		echo '</tbody></table>';
	}

	private function render_payload( $payload ) {
		if ( empty( $payload ) ) {
			echo '<p>' . esc_html__( 'This journey has no stored datapoints.', 'bw-lead-ai' ) . '</p>';
			return;
		}

		$multiline = array( 'summary', 'summary_detailed' );

		echo '<h2>' . esc_html__( 'Attribution', 'bw-lead-ai' ) . '</h2>';
		echo '<table class="widefat striped" style="max-width:760px;"><tbody>';
		foreach ( $payload as $key => $value ) {
			if ( in_array( $key, $multiline, true ) ) {
				continue;
			}
			echo '<tr><th scope="row" style="width:180px;"><code>' . esc_html( '{bw:' . $key . '}' ) . '</code></th>';
			echo '<td>' . esc_html( (string) $value ) . '</td></tr>';
		}
		echo '</tbody></table>';

		foreach ( $multiline as $key ) {
			if ( empty( $payload[ $key ] ) ) {
				continue;
			}
			echo '<h2 style="margin-top:24px;">' . esc_html( '{bw:' . $key . '}' ) . '</h2>';
			// Escape first, then break — the same ordering the Gravity Forms entry
			// renderer needs. Never the other way round.
			echo '<div class="bw-lead-ai-panel" style="max-width:760px;font-family:monospace;font-size:12px;line-height:1.6;">';
			echo nl2br( esc_html( (string) $payload[ $key ] ) );
			echo '</div>';
		}
	}
}
