<?php
/**
 * Verification page shortcodes.
 *
 * [govalid_verify_result]       — Full verification result card.
 * [govalid_verify_status]       — Status badge only.
 * [govalid_verify_field field]  — Single field (name, type, creator, institution, scans, status, created).
 * [govalid_verify_alerts]       — Security alert list.
 * [govalid_powered_by]          — "Powered by GoValid" badge.
 *
 * All shortcodes share a single cached API response per page load.
 *
 * @package GoValid_QR
 */

defined( 'ABSPATH' ) || exit;

class GoValid_Verify_Page_Shortcode {

	/** @var array|null Cached API response for the current request. */
	private static $api_data = null;

	/** @var bool Whether we already attempted the API call. */
	private static $api_fetched = false;

	/**
	 * Register all verification-page shortcodes.
	 */
	public function register(): void {
		add_shortcode( 'govalid_verify_result', array( $this, 'render_full_result' ) );
		add_shortcode( 'govalid_verify_status', array( $this, 'render_status' ) );
		add_shortcode( 'govalid_verify_field', array( $this, 'render_field' ) );
		add_shortcode( 'govalid_verify_alerts', array( $this, 'render_alerts' ) );
		add_shortcode( 'govalid_powered_by', array( $this, 'render_powered_by' ) );
	}

	// ------------------------------------------------------------------
	// Data layer
	// ------------------------------------------------------------------

	/**
	 * Fetch and cache the verification response from GoValid API.
	 *
	 * @return array|null Parsed response or null on failure.
	 */
	private function get_verification_data() {
		if ( self::$api_fetched ) {
			return self::$api_data;
		}
		self::$api_fetched = true;

		$token = get_query_var( 'govalid_verify_token', '' );
		if ( ! $token ) {
			self::$api_data = array( 'error' => 'no_token' );
			return self::$api_data;
		}

		$token   = sanitize_text_field( rtrim( $token, '/' ) );
		$api_url = GoValid_QR::get_base_url() . '/api/v1/ojs/verify-signed/?token=' . rawurlencode( $token );

		$response = wp_remote_get( $api_url, array(
			'timeout' => 15,
			'headers' => array( 'Accept' => 'application/json' ),
		) );

		if ( is_wp_error( $response ) ) {
			self::$api_data = array( 'error' => 'connection' );
			return self::$api_data;
		}

		$status_code = wp_remote_retrieve_response_code( $response );
		$body        = json_decode( wp_remote_retrieve_body( $response ), true );

		// Unwrap OJS envelope if present: { status, content: { success, verified, ... } }
		if ( isset( $body['content'] ) && is_array( $body['content'] ) ) {
			$body = $body['content'];
		}

		if ( 200 === $status_code && ! empty( $body['success'] ) && ! empty( $body['verified'] ) ) {
			self::$api_data = $body;
			return self::$api_data;
		}

		// Map HTTP status to error type.
		if ( 404 === $status_code ) {
			self::$api_data = array( 'error' => 'not_found' );
		} elseif ( 429 === $status_code ) {
			self::$api_data = array( 'error' => 'rate_limit' );
		} else {
			self::$api_data = array( 'error' => 'failed', 'body' => $body );
		}

		return self::$api_data;
	}

	/**
	 * Determine the display status of a QR code.
	 *
	 * @param array $data API data.
	 * @return string verified|counterfeit|warning|inactive|error
	 */
	private function get_status_key( array $data ): string {
		if ( ! empty( $data['error'] ) ) {
			return 'error';
		}

		$qr = isset( $data['qr_code'] ) ? $data['qr_code'] : array();
		$ac = isset( $data['anti_counterfeit'] ) ? $data['anti_counterfeit'] : array();

		if ( ! empty( $ac['has_violations'] ) && 'block' === ( $ac['action'] ?? '' ) ) {
			return 'counterfeit';
		}
		if ( ! empty( $ac['has_violations'] ) ) {
			return 'warning';
		}
		if ( empty( $qr['is_active'] ) ) {
			return 'inactive';
		}

		return 'verified';
	}

	// ------------------------------------------------------------------
	// Shortcode: [govalid_verify_result]
	// ------------------------------------------------------------------

	/**
	 * Render the full verification result card.
	 *
	 * @param array|string $atts Shortcode attributes.
	 * @return string
	 */
	public function render_full_result( $atts = array() ): string {
		$this->enqueue_styles();

		$data = $this->get_verification_data();

		if ( ! $data || ! empty( $data['error'] ) ) {
			return $this->render_error_card( $data );
		}

		$qr      = isset( $data['qr_code'] ) ? $data['qr_code'] : array();
		$creator = isset( $data['creator'] ) ? $data['creator'] : array();
		$ac      = isset( $data['anti_counterfeit'] ) ? $data['anti_counterfeit'] : array();
		$status  = $this->get_status_key( $data );

		ob_start();
		?>
		<div class="govalid-verify-wrapper">
			<div class="govalid-verify-card">
				<?php
			// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- SVG markup; dynamic values escaped in build_status_section().
			echo $this->build_status_section( $status, $qr );
			?>

				<div class="govalid-verify-body">
					<table class="govalid-verify-details">
						<?php if ( ! empty( $qr['name'] ) ) : ?>
							<tr>
								<th><?php esc_html_e( 'QR Name', 'govalid-qr' ); ?></th>
								<td><?php echo esc_html( $qr['name'] ); ?></td>
							</tr>
						<?php endif; ?>
						<tr>
							<th><?php esc_html_e( 'Type', 'govalid-qr' ); ?></th>
							<td><?php echo esc_html( $this->format_type( $qr ) ); ?></td>
						</tr>
						<tr>
							<th><?php esc_html_e( 'Created by', 'govalid-qr' ); ?></th>
							<td><?php echo esc_html( $this->format_creator( $creator ) ); ?></td>
						</tr>
						<?php if ( ! empty( $creator['institution_name'] ) ) : ?>
							<tr>
								<th><?php esc_html_e( 'Institution', 'govalid-qr' ); ?></th>
								<td><?php echo esc_html( $creator['institution_name'] ); ?></td>
							</tr>
						<?php endif; ?>
						<tr>
							<th><?php esc_html_e( 'Scan count', 'govalid-qr' ); ?></th>
							<td><?php echo esc_html( number_format_i18n( isset( $qr['scan_count'] ) ? $qr['scan_count'] : 0 ) ); ?></td>
						</tr>
						<tr>
							<th><?php esc_html_e( 'Status', 'govalid-qr' ); ?></th>
							<td>
								<?php if ( ! empty( $qr['is_active'] ) ) : ?>
									<span class="govalid-verify-badge active"><?php esc_html_e( 'Active', 'govalid-qr' ); ?></span>
								<?php else : ?>
									<span class="govalid-verify-badge inactive"><?php esc_html_e( 'Inactive', 'govalid-qr' ); ?></span>
								<?php endif; ?>
							</td>
						</tr>
						<?php if ( ! empty( $qr['created_at'] ) ) : ?>
							<tr>
								<th><?php esc_html_e( 'Created', 'govalid-qr' ); ?></th>
								<td><?php echo esc_html( wp_date( get_option( 'date_format' ), strtotime( $qr['created_at'] ) ) ); ?></td>
							</tr>
						<?php endif; ?>
					</table>

					<?php
					$qr_fields = isset( $data['qr_data'] ) && is_array( $data['qr_data'] ) ? $data['qr_data'] : array();
					if ( ! empty( $qr_fields ) ) {
						echo wp_kses_post( $this->build_qr_data_section( $qr_fields ) );
					}
					?>

					<?php echo wp_kses_post( $this->build_violations_section( $ac ) ); ?>
				</div>

				<?php if ( get_option( 'govalid_qr_show_powered_by', '' ) === '1' ) : ?>
				<div class="govalid-verify-footer">
					<?php echo wp_kses_post( $this->build_powered_by() ); ?>
				</div>
				<?php endif; ?>
			</div>
		</div>
		<?php
		return ob_get_clean();
	}

	// ------------------------------------------------------------------
	// Shortcode: [govalid_verify_status]
	// ------------------------------------------------------------------

	/**
	 * Render only the status indicator.
	 *
	 * @return string
	 */
	public function render_status(): string {
		$this->enqueue_styles();

		$data = $this->get_verification_data();

		if ( ! $data || ! empty( $data['error'] ) ) {
			return $this->render_error_card( $data );
		}

		$qr     = isset( $data['qr_code'] ) ? $data['qr_code'] : array();
		$status = $this->get_status_key( $data );

		return '<div class="govalid-verify-wrapper">' . $this->build_status_section( $status, $qr ) . '</div>';
	}

	// ------------------------------------------------------------------
	// Shortcode: [govalid_verify_field field="name"]
	// ------------------------------------------------------------------

	/**
	 * Render a single QR code field.
	 *
	 * Supported fields: name, type, creator, institution, scans, status, created.
	 *
	 * @param array|string $atts Shortcode attributes.
	 * @return string
	 */
	public function render_field( $atts = array() ): string {
		$atts = shortcode_atts( array(
			'field' => 'name',
		), $atts, 'govalid_verify_field' );

		$data = $this->get_verification_data();
		if ( ! $data || ! empty( $data['error'] ) ) {
			return '';
		}

		$qr      = isset( $data['qr_code'] ) ? $data['qr_code'] : array();
		$creator = isset( $data['creator'] ) ? $data['creator'] : array();

		switch ( $atts['field'] ) {
			case 'name':
				return esc_html( isset( $qr['name'] ) ? $qr['name'] : '-' );
			case 'type':
				return esc_html( $this->format_type( $qr ) );
			case 'creator':
				return esc_html( $this->format_creator( $creator ) );
			case 'institution':
				return esc_html( isset( $creator['institution_name'] ) ? $creator['institution_name'] : '-' );
			case 'scans':
				return esc_html( number_format_i18n( isset( $qr['scan_count'] ) ? $qr['scan_count'] : 0 ) );
			case 'status':
				$this->enqueue_styles();
				if ( ! empty( $qr['is_active'] ) ) {
					return '<span class="govalid-verify-badge active">' . esc_html__( 'Active', 'govalid-qr' ) . '</span>';
				}
				return '<span class="govalid-verify-badge inactive">' . esc_html__( 'Inactive', 'govalid-qr' ) . '</span>';
			case 'created':
				if ( ! empty( $qr['created_at'] ) ) {
					return esc_html( wp_date( get_option( 'date_format' ), strtotime( $qr['created_at'] ) ) );
				}
				return '-';
			default:
				return '';
		}
	}

	// ------------------------------------------------------------------
	// Shortcode: [govalid_verify_alerts]
	// ------------------------------------------------------------------

	/**
	 * Render security alerts / violations.
	 *
	 * @return string
	 */
	public function render_alerts(): string {
		$data = $this->get_verification_data();
		if ( ! $data || ! empty( $data['error'] ) ) {
			return '';
		}

		$ac = isset( $data['anti_counterfeit'] ) ? $data['anti_counterfeit'] : array();
		$this->enqueue_styles();

		return $this->build_violations_section( $ac );
	}

	// ------------------------------------------------------------------
	// Shortcode: [govalid_powered_by]
	// ------------------------------------------------------------------

	/**
	 * Render the "Powered by GoValid" badge.
	 *
	 * @return string
	 */
	public function render_powered_by(): string {
		if ( get_option( 'govalid_qr_show_powered_by', '' ) !== '1' ) {
			return '';
		}
		$this->enqueue_styles();
		return '<div class="govalid-verify-footer">' . $this->build_powered_by() . '</div>';
	}

	// ------------------------------------------------------------------
	// Builders (shared by multiple shortcodes)
	// ------------------------------------------------------------------

	/**
	 * Build the status section HTML.
	 *
	 * @param string $status Status key.
	 * @param array  $qr     QR code data.
	 * @return string
	 */
	private function build_status_section( string $status, array $qr ): string {
		$map = array(
			'verified'    => array(
				'class'    => 'verified',
				'icon'     => '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>',
				'title'    => __( 'Verified Authentic', 'govalid-qr' ),
				'subtitle' => __( 'This QR code is genuine and active.', 'govalid-qr' ),
			),
			'counterfeit' => array(
				'class'    => 'counterfeit',
				'icon'     => '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>',
				'title'    => __( 'Counterfeit Detected', 'govalid-qr' ),
				'subtitle' => __( 'This QR code has security violations.', 'govalid-qr' ),
			),
			'warning'     => array(
				'class'    => 'inactive',
				'icon'     => '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>',
				'title'    => __( 'Verified with Warnings', 'govalid-qr' ),
				'subtitle' => __( 'This QR code is valid but has security warnings.', 'govalid-qr' ),
			),
			'inactive'    => array(
				'class'    => 'inactive',
				'icon'     => '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>',
				'title'    => __( 'QR Code Inactive', 'govalid-qr' ),
				'subtitle' => __( 'This QR code has been deactivated by its owner.', 'govalid-qr' ),
			),
		);

		$info = isset( $map[ $status ] ) ? $map[ $status ] : $map['verified'];

		return '<div class="govalid-verify-status ' . esc_attr( $info['class'] ) . '">'
			. '<div class="govalid-verify-status-icon">' . $info['icon'] . '</div>'
			. '<h2>' . esc_html( $info['title'] ) . '</h2>'
			. '<p class="govalid-verify-subtitle">' . esc_html( $info['subtitle'] ) . '</p>'
			. '</div>';
	}

	/**
	 * Build the violations section HTML.
	 *
	 * @param array $ac Anti-counterfeit data.
	 * @return string
	 */
	private function build_violations_section( array $ac ): string {
		if ( empty( $ac['has_violations'] ) || empty( $ac['violations'] ) ) {
			return '';
		}

		$html = '<div class="govalid-verify-violations">';
		$html .= '<h3>' . esc_html__( 'Security Alerts', 'govalid-qr' ) . '</h3>';

		foreach ( $ac['violations'] as $v ) {
			$html .= '<div class="govalid-verify-violation-item">';
			$html .= '<strong>' . esc_html( isset( $v['title'] ) ? $v['title'] : $v['type'] ) . '</strong>';
			if ( ! empty( $v['message'] ) ) {
				$html .= esc_html( $v['message'] );
			}
			$html .= '</div>';
		}

		$html .= '</div>';
		return $html;
	}

	/**
	 * Build the QR data fields section HTML.
	 *
	 * @param array $fields QR data key-value pairs.
	 * @return string
	 */
	private function build_qr_data_section( array $fields ): string {
		if ( empty( $fields ) ) {
			return '';
		}

		// Human-readable labels for known keys.
		$labels = array(
			'type'               => __( 'Data Type', 'govalid-qr' ),
			'url'                => __( 'URL', 'govalid-qr' ),
			'text'               => __( 'Text', 'govalid-qr' ),
			'email'              => __( 'Email', 'govalid-qr' ),
			'phone'              => __( 'Phone', 'govalid-qr' ),
			'subject'            => __( 'Subject', 'govalid-qr' ),
			'body'               => __( 'Body', 'govalid-qr' ),
			'message'            => __( 'Message', 'govalid-qr' ),
			'ssid'               => __( 'WiFi SSID', 'govalid-qr' ),
			'encryption'         => __( 'Encryption', 'govalid-qr' ),
			'hidden'             => __( 'Hidden Network', 'govalid-qr' ),
			'first_name'         => __( 'First Name', 'govalid-qr' ),
			'last_name'          => __( 'Last Name', 'govalid-qr' ),
			'full_name'          => __( 'Full Name', 'govalid-qr' ),
			'organization'       => __( 'Organization', 'govalid-qr' ),
			'title'              => __( 'Title', 'govalid-qr' ),
			'address'            => __( 'Address', 'govalid-qr' ),
			'website'            => __( 'Website', 'govalid-qr' ),
			'note'               => __( 'Note', 'govalid-qr' ),
			'latitude'           => __( 'Latitude', 'govalid-qr' ),
			'longitude'          => __( 'Longitude', 'govalid-qr' ),
			'description'        => __( 'Description', 'govalid-qr' ),
			'product_name'       => __( 'Product Name', 'govalid-qr' ),
			'batch_number'       => __( 'Batch Number', 'govalid-qr' ),
			'serial_number'      => __( 'Serial Number', 'govalid-qr' ),
			'manufacture_date'   => __( 'Manufacture Date', 'govalid-qr' ),
			'expiry_date'        => __( 'Expiry Date', 'govalid-qr' ),
			'country_of_origin'  => __( 'Country of Origin', 'govalid-qr' ),
			'manufacturer'       => __( 'Manufacturer', 'govalid-qr' ),
			'brand'              => __( 'Brand', 'govalid-qr' ),
			'category'           => __( 'Category', 'govalid-qr' ),
			'weight'             => __( 'Weight', 'govalid-qr' ),
			'dimensions'         => __( 'Dimensions', 'govalid-qr' ),
			'ingredients'        => __( 'Ingredients', 'govalid-qr' ),
			'certifications'     => __( 'Certifications', 'govalid-qr' ),
		);

		// Keys to skip (internal / already displayed).
		$skip = array( 'name', 'qr_name', 'qr_type', 'template_type', 'security_level', 'generated_via' );

		$html  = '<div class="govalid-verify-data">';
		$html .= '<h3>' . esc_html__( 'QR Code Data', 'govalid-qr' ) . '</h3>';
		$html .= '<table class="govalid-verify-data-table">';

		foreach ( $fields as $key => $value ) {
			if ( in_array( $key, $skip, true ) ) {
				continue;
			}

			// Skip complex nested values (arrays/objects) — show only scalar.
			if ( is_array( $value ) || is_object( $value ) ) {
				// For timeline_entries or similar, show count.
				if ( is_array( $value ) && ! empty( $value ) ) {
					$value = sprintf(
						/* translators: %d: number of items */
						_n( '%d entry', '%d entries', count( $value ), 'govalid-qr' ),
						count( $value )
					);
				} else {
					continue;
				}
			}

			// Skip empty-ish values.
			$str_val = (string) $value;
			if ( '' === $str_val || 'null' === $str_val ) {
				continue;
			}

			$label = isset( $labels[ $key ] ) ? $labels[ $key ] : ucwords( str_replace( '_', ' ', $key ) );

			// Auto-link URLs.
			$display = esc_html( $str_val );
			if ( filter_var( $str_val, FILTER_VALIDATE_URL ) ) {
				$display = '<a href="' . esc_url( $str_val ) . '" target="_blank" rel="noopener">' . esc_html( $str_val ) . '</a>';
			} elseif ( filter_var( $str_val, FILTER_VALIDATE_EMAIL ) ) {
				$display = '<a href="mailto:' . esc_attr( $str_val ) . '">' . esc_html( $str_val ) . '</a>';
			}

			$html .= '<tr><th>' . esc_html( $label ) . '</th><td>' . $display . '</td></tr>';
		}

		$html .= '</table></div>';

		return $html;
	}

	/**
	 * Build the "Powered by GoValid" badge HTML.
	 *
	 * @return string
	 */
	private function build_powered_by(): string {
		return '<a href="https://govalid.org" target="_blank" rel="noopener" class="govalid-powered-by">'
			. '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="20" height="20">'
			. '<circle cx="128" cy="128" r="128" fill="#4169E1"/>'
			. '<path fill="#ffffff" fill-rule="evenodd" d="M225.86,102.82c-3.77-3.94-7.67-8-9.14-11.57-1.36-3.27-1.44-8.69-1.52-13.94-.15-9.76-.31-20.82-8-28.51s-18.75-7.85-28.51-8c-5.25-.08-10.67-.16-13.94-1.52-3.56-1.47-7.63-5.37-11.57-9.14C146.28,23.51,138.44,16,128,16s-18.27,7.51-25.18,14.14c-3.94,3.77-8,7.67-11.57,9.14C88,40.64,82.56,40.72,77.31,40.8c-9.76.15-20.82.31-28.51,8S41,67.55,40.8,77.31c-.08,5.25-.16,10.67-1.52,13.94-1.47,3.56-5.37,7.63-9.14,11.57C23.51,109.72,16,117.56,16,128s7.51,18.27,14.14,25.18c3.77,3.94,7.67,8,9.14,11.57,1.36,3.27,1.44,8.69,1.52,13.94.15,9.76.31,20.82,8,28.51s18.75,7.85,28.51,8c5.25.08,10.67.16,13.94,1.52,3.56,1.47,7.63,5.37,11.57,9.14C109.72,232.49,117.56,240,128,240s18.27-7.51,25.18-14.14c3.94-3.77,8-7.67,11.57-9.14,3.27-1.36,8.69-1.44,13.94-1.52,9.76-.15,20.82-.31,28.51-8s7.85-18.75,8-28.51c.08-5.25.16-10.67,1.52-13.94,1.47-3.56,5.37-7.63,9.14-11.57C232.49,146.28,240,138.44,240,128S232.49,109.73,225.86,102.82Zm-52.2,6.84-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35a8,8,0,0,1,11.32,11.32Z"/>'
			. '</svg> '
			/* translators: Do not remove — required branding. */
			. esc_html__( 'Powered by', 'govalid-qr' )
			. ' <span>GoValid</span></a>';
	}

	// ------------------------------------------------------------------
	// Error card
	// ------------------------------------------------------------------

	/**
	 * Render an error card.
	 *
	 * @param array|null $data Error data.
	 * @return string
	 */
	private function render_error_card( $data ): string {
		$error_type = is_array( $data ) && isset( $data['error'] ) ? $data['error'] : 'unknown';

		$messages = array(
			'no_token'   => array(
				'title'   => __( 'No Token Provided', 'govalid-qr' ),
				'message' => __( 'No QR code token was found in the URL.', 'govalid-qr' ),
			),
			'not_found'  => array(
				'title'   => __( 'QR Code Not Found', 'govalid-qr' ),
				'message' => __( 'This QR code was not found in the GoValid system.', 'govalid-qr' ),
			),
			'rate_limit' => array(
				'title'   => __( 'Too Many Requests', 'govalid-qr' ),
				'message' => __( 'Please wait a moment and try again.', 'govalid-qr' ),
			),
			'connection' => array(
				'title'   => __( 'Connection Error', 'govalid-qr' ),
				'message' => __( 'Unable to connect to the verification server. Please try again later.', 'govalid-qr' ),
			),
		);

		$msg = isset( $messages[ $error_type ] )
			? $messages[ $error_type ]
			: array(
				'title'   => __( 'Verification Failed', 'govalid-qr' ),
				'message' => __( 'Unable to verify this QR code. Please try again later.', 'govalid-qr' ),
			);

		return '<div class="govalid-verify-wrapper"><div class="govalid-verify-card">'
			. '<div class="govalid-verify-body">'
			. '<div class="govalid-verify-error" style="display:block;">'
			. '<div class="govalid-verify-error-icon">'
			. '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">'
			. '<circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line>'
			. '</svg></div>'
			. '<h2>' . esc_html( $msg['title'] ) . '</h2>'
			. '<p>' . esc_html( $msg['message'] ) . '</p>'
			. '</div></div>'
			. ( get_option( 'govalid_qr_show_powered_by', '' ) === '1' ? '<div class="govalid-verify-footer">' . $this->build_powered_by() . '</div>' : '' )
			. '</div></div>';
	}

	// ------------------------------------------------------------------
	// Helpers
	// ------------------------------------------------------------------

	/**
	 * Format QR type for display.
	 *
	 * @param array $qr QR code data.
	 * @return string
	 */
	private function format_type( array $qr ): string {
		$types = array(
			'url'      => 'URL',
			'vcard'    => 'vCard',
			'wifi'     => 'WiFi',
			'text'     => 'Text',
			'email'    => 'Email',
			'sms'      => 'SMS',
			'phone'    => 'Phone',
			'location' => 'Location',
		);

		$qr_type = isset( $qr['qr_type'] ) ? $qr['qr_type'] : '';
		if ( isset( $types[ $qr_type ] ) ) {
			return $types[ $qr_type ];
		}

		return $qr_type ? $qr_type : ( isset( $qr['template_type'] ) ? $qr['template_type'] : '-' );
	}

	/**
	 * Format creator name for display.
	 *
	 * @param array $creator Creator data.
	 * @return string
	 */
	private function format_creator( array $creator ): string {
		$name = '';
		if ( ! empty( $creator['full_name'] ) ) {
			$name = $creator['full_name'];
		} elseif ( ! empty( $creator['username'] ) ) {
			$name = $creator['username'];
		} else {
			return '-';
		}

		if ( ! empty( $creator['is_verified'] ) || ! empty( $creator['staff_verified'] ) ) {
			$name .= ' ✓';
		}

		return $name;
	}

	/**
	 * Enqueue verification-page styles (once).
	 */
	private function enqueue_styles(): void {
		if ( wp_style_is( 'govalid-verify-page', 'enqueued' ) ) {
			return;
		}
		wp_enqueue_style(
			'govalid-verify-page',
			GOVALID_QR_PLUGIN_URL . 'public/css/verify-page.css',
			array(),
			GOVALID_QR_VERSION
		);
	}
}
