<?php
/**
 * Merge tag registry for Mail Doctor email templates.
 *
 * @package MailDoctor
 */

namespace MailDoctor\Email;

use WC_DateTime;
use WC_Order;

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

/**
 * Provides a centralized registry of merge tags with metadata and resolvers.
 */
class MergeTagRegistry {
	public const VERSION = '1.0.0';

	/**
	 * Provide all merge tag callbacks keyed by tag identifier.
	 *
	 * @return array<string, callable>
	 */
	public static function get_all(): array {
		$definitions = self::get_definitions();
		$callbacks   = array();

		foreach ( $definitions as $tag => $definition ) {
			if ( empty( $definition['callback'] ) || ! is_callable( $definition['callback'] ) ) {
				continue;
			}

			$callbacks[ $tag ] = $definition['callback'];
		}

		/**
		 * Allow third parties to adjust merge tag callbacks.
		 *
		 * @param array<string, callable> $callbacks Registered callbacks.
		 */
		return apply_filters( 'mail_doctor_email_merge_tag_callbacks', $callbacks );
	}

	/**
	 * Retrieve documented definitions for merge tags.
	 *
	 * @return array<string, array<string, mixed>>
	 */
	public static function get_definitions(): array {
		static $definitions = null;

		if ( null === $definitions ) {
			$definitions = self::bootstrap_definitions();
		}

		/**
		 * Filter merge tag definitions prior to usage.
		 *
		 * @param array<string, array<string, mixed>> $definitions Merge tag metadata.
		 */
		return apply_filters( 'mail_doctor_email_merge_tag_definitions', $definitions );
	}

	/**
	 * Resolve a single merge tag to a sanitized string.
	 *
	 * @param string               $tag         Merge tag identifier.
	 * @param array<string, mixed> $context     Rendering context.
	 * @param array<string, mixed> $filter_args Optional legacy filter arguments.
	 * @return string
	 */
	public static function resolve( string $tag, array $context, array $filter_args = array() ): string {
		$tag = self::normalize_tag( $tag );

		if ( '' === $tag ) {
			return '';
		}

		$callbacks   = self::get_all();
		$definitions = self::get_definitions();
		$sanitizer   = $definitions[ $tag ]['sanitize'] ?? 'text';
		$value       = '';

		if ( isset( $callbacks[ $tag ] ) ) {
			try {
				$value = $callbacks[ $tag ]( $context );
			} catch ( \Throwable $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
				$value = '';
			}
		} elseif ( isset( $context['legacy_tokens'][ $tag ] ) ) {
			$value = $context['legacy_tokens'][ $tag ];
		}

		return self::sanitize_value( $tag, $value, $sanitizer );
	}

	/**
	 * Resolve all merge tags in the registry for the provided context.
	 *
	 * @param array<string, mixed> $context     Rendering context.
	 * @param array<string, mixed> $filter_args Optional legacy filter arguments.
	 * @return array<string, string>
	 */
	public static function resolve_all( array $context, array $filter_args = array() ): array {
		$resolved    = array();
		$callbacks   = self::get_all();
		$definitions = self::get_definitions();

		foreach ( $callbacks as $tag => $callback ) {
			try {
				$value = $callback( $context );
			} catch ( \Throwable $exception ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
				$value = '';
			}

			$resolved[ $tag ] = self::sanitize_value(
				$tag,
				$value,
				$definitions[ $tag ]['sanitize'] ?? 'text'
			);
		}

		if ( isset( $context['legacy_tokens'] ) && is_array( $context['legacy_tokens'] ) ) {
			foreach ( $context['legacy_tokens'] as $legacy_tag => $legacy_value ) {
				$legacy_tag = self::normalize_tag( (string) $legacy_tag );
				if ( '' === $legacy_tag || isset( $resolved[ $legacy_tag ] ) ) {
					continue;
				}

				$resolved[ $legacy_tag ] = self::sanitize_value(
					$legacy_tag,
					$legacy_value,
					$definitions[ $legacy_tag ]['sanitize'] ?? 'text'
				);
			}
		}

		$email_type    = isset( $filter_args['email_type'] ) ? (string) $filter_args['email_type'] : '';
		$order_context = isset( $filter_args['order'] ) && $filter_args['order'] instanceof WC_Order ? $filter_args['order'] : null;
		$token_context = isset( $filter_args['token_context'] ) && is_array( $filter_args['token_context'] ) ? $filter_args['token_context'] : array();

		/**
		 * Preserve legacy filter signature for merge tag values.
		 *
		 * @param array<string, string> $resolved      Merge tag map.
		 * @param string                $email_type    Email identifier.
		 * @param WC_Order|null         $order_context Order instance.
		 * @param array<string, mixed>  $token_context Additional token context.
		 */
		$filtered = apply_filters(
			'mail_doctor_email_merge_tags',
			$resolved,
			$email_type,
			$order_context,
			$token_context
		);

		if ( is_array( $filtered ) ) {
			$resolved = array();

			foreach ( $filtered as $tag => $value ) {
				$tag = self::normalize_tag( (string) $tag );

				if ( '' === $tag ) {
					continue;
				}

				$resolved[ $tag ] = self::sanitize_value(
					$tag,
					$value,
					$definitions[ $tag ]['sanitize'] ?? 'text'
				);
			}
		}

		return $resolved;
	}

	/**
	 * Build the documented merge tag definitions.
	 *
	 * @return array<string, array<string, mixed>>
	 */
	protected static function bootstrap_definitions(): array {
		$definitions = array();

		// Order tags.
		$definitions['order.id'] = self::definition(
			'order',
			esc_html__( 'Order ID', 'mail-doctor' ),
			esc_html__( 'Internal numeric order ID.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				$order = self::order( $context );
				if ( $order instanceof WC_Order ) {
					return (string) $order->get_id();
				}

				return self::sample( $context, 'order', 'id' );
			}
		);
		self::register_alias( $definitions, 'order.id', 'order_id' );

		$definitions['order.number'] = self::definition(
			'order',
			esc_html__( 'Order number', 'mail-doctor' ),
			esc_html__( 'Public order reference used in emails.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				$order = self::order( $context );
				if ( $order instanceof WC_Order ) {
					return (string) $order->get_order_number();
				}

				return self::sample( $context, 'order', 'number' );
			}
		);
		self::register_alias( $definitions, 'order.number', 'order_number' );

		$definitions['order.status'] = self::definition(
			'order',
			esc_html__( 'Order status', 'mail-doctor' ),
			esc_html__( 'Human-readable status label.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				$order = self::order( $context );
				if ( $order instanceof WC_Order ) {
					$status = $order->get_status();
					if ( function_exists( 'wc_get_order_status_name' ) ) {
						return (string) wc_get_order_status_name( $status );
					}

					return ucfirst( (string) $status );
				}

				return self::sample( $context, 'order', 'status' );
			}
		);
		self::register_alias( $definitions, 'order.status', 'order_status' );

		$definitions['order.date'] = self::definition(
			'order',
			esc_html__( 'Order date', 'mail-doctor' ),
			esc_html__( 'Localized order creation date.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				$order = self::order( $context );
				if ( $order instanceof WC_Order ) {
					$created = method_exists( $order, 'get_date_created' ) ? $order->get_date_created() : null;
					if ( $created instanceof WC_DateTime ) {
						if ( function_exists( 'wc_format_datetime' ) ) {
							return (string) wc_format_datetime( $created );
						}

						return $created->date_i18n( get_option( 'date_format', 'Y-m-d' ) . ' ' . get_option( 'time_format', 'H:i' ) );
					}
				}

				return self::sample( $context, 'order', 'date' );
			}
		);
		self::register_alias( $definitions, 'order.date', 'order_date' );

		$definitions['order.total'] = self::definition(
			'order',
			esc_html__( 'Order total', 'mail-doctor' ),
			esc_html__( 'Final order total including tax and shipping.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				$order = self::order( $context );
				if ( $order instanceof WC_Order ) {
					return wp_strip_all_tags( $order->get_formatted_order_total() );
				}

				return self::legacy( $context, 'order_total', 'order', 'total' );
			}
		);
		self::register_alias( $definitions, 'order.total', 'order_total' );

		$definitions['order.currency'] = self::definition(
			'order',
			esc_html__( 'Order currency', 'mail-doctor' ),
			esc_html__( 'Currency code used for the order.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				$order = self::order( $context );
				if ( $order instanceof WC_Order ) {
					return (string) $order->get_currency();
				}

				return self::sample( $context, 'order', 'currency' );
			}
		);
		self::register_alias( $definitions, 'order.currency', 'order_currency' );

		$definitions['order.payment_method'] = self::definition(
			'order',
			esc_html__( 'Payment method', 'mail-doctor' ),
			esc_html__( 'Payment gateway title.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				return self::legacy( $context, 'payment_method', 'order', 'payment_method' );
			}
		);
		self::register_alias( $definitions, 'order.payment_method', 'payment_method' );

		$definitions['order.shipping_method'] = self::definition(
			'order',
			esc_html__( 'Shipping method', 'mail-doctor' ),
			esc_html__( 'Selected shipping method(s).', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				return self::legacy( $context, 'shipping_method', 'order', 'shipping_method' );
			}
		);
		self::register_alias( $definitions, 'order.shipping_method', 'shipping_method' );

		$definitions['order.items_table'] = self::definition(
			'order',
			esc_html__( 'Items table', 'mail-doctor' ),
			esc_html__( 'HTML table of order line items.', 'mail-doctor' ),
			'html',
			static function ( array $context ): string {
				return self::legacy( $context, 'items_table', 'order', 'items_table' );
			}
		);
		self::register_alias( $definitions, 'order.items_table', 'items_table' );

		$definitions['order.download_links'] = self::definition(
			'order',
			esc_html__( 'Download links', 'mail-doctor' ),
			esc_html__( 'List of downloadable product links.', 'mail-doctor' ),
			'html',
			static function ( array $context ): string {
				return self::legacy( $context, 'download_links', 'order', 'download_links' );
			}
		);
		self::register_alias( $definitions, 'order.download_links', 'download_links' );

		$definitions['order.refund_total'] = self::definition(
			'order',
			esc_html__( 'Refund amount', 'mail-doctor' ),
			esc_html__( 'Formatted refunded total.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				return self::legacy( $context, 'refund_amount', 'order', 'refund_amount' );
			}
		);
		self::register_alias( $definitions, 'order.refund_total', 'refund_amount' );

		$definitions['order.billing_address'] = self::definition(
			'order',
			esc_html__( 'Billing address', 'mail-doctor' ),
			esc_html__( 'Formatted billing address with line breaks.', 'mail-doctor' ),
			'html',
			static function ( array $context ): string {
				return self::legacy( $context, 'billing_address', 'order', 'billing_address' );
			}
		);
		self::register_alias( $definitions, 'order.billing_address', 'billing_address' );

		$definitions['order.shipping_address'] = self::definition(
			'order',
			esc_html__( 'Shipping address', 'mail-doctor' ),
			esc_html__( 'Formatted shipping address with line breaks.', 'mail-doctor' ),
			'html',
			static function ( array $context ): string {
				return self::legacy( $context, 'shipping_address', 'order', 'shipping_address' );
			}
		);
		self::register_alias( $definitions, 'order.shipping_address', 'shipping_address' );

		$definitions['order.view_url'] = self::definition(
			'order',
			esc_html__( 'Order view URL', 'mail-doctor' ),
			esc_html__( 'Customer-facing order view link.', 'mail-doctor' ),
			'url',
			static function ( array $context ): string {
				$order = self::order( $context );
				if ( $order instanceof WC_Order && is_callable( array( $order, 'get_view_order_url' ) ) ) {
					return (string) $order->get_view_order_url();
				}

				return self::sample( $context, 'order', 'view_url' );
			}
		);

		// Customer tags.
		$definitions['customer.first_name'] = self::definition(
			'customer',
			esc_html__( 'Customer first name', 'mail-doctor' ),
			esc_html__( 'Customer billing first name.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				$customer = self::customer( $context );
				if ( isset( $customer['first_name'] ) && '' !== $customer['first_name'] ) {
					return (string) $customer['first_name'];
				}

				$order = self::order( $context );
				if ( $order instanceof WC_Order ) {
					return (string) $order->get_billing_first_name();
				}

				return self::sample( $context, 'customer', 'first_name' );
			}
		);
		self::register_alias( $definitions, 'customer.first_name', 'customer_first_name' );

		$definitions['customer.last_name'] = self::definition(
			'customer',
			esc_html__( 'Customer last name', 'mail-doctor' ),
			esc_html__( 'Customer billing last name.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				$customer = self::customer( $context );
				if ( isset( $customer['last_name'] ) && '' !== $customer['last_name'] ) {
					return (string) $customer['last_name'];
				}

				$order = self::order( $context );
				if ( $order instanceof WC_Order ) {
					return (string) $order->get_billing_last_name();
				}

				return self::sample( $context, 'customer', 'last_name' );
			}
		);
		self::register_alias( $definitions, 'customer.last_name', 'customer_last_name' );

		$definitions['customer.full_name'] = self::definition(
			'customer',
			esc_html__( 'Customer full name', 'mail-doctor' ),
			esc_html__( 'Combined billing first and last name.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				$customer = self::customer( $context );
				if ( isset( $customer['full_name'] ) && '' !== trim( (string) $customer['full_name'] ) ) {
					return (string) $customer['full_name'];
				}

				$order = self::order( $context );
				if ( $order instanceof WC_Order ) {
					return trim( $order->get_formatted_billing_full_name() ?: $order->get_billing_first_name() );
				}

				return self::sample( $context, 'customer', 'full_name' );
			}
		);
		self::register_alias( $definitions, 'customer.full_name', 'customer_name' );

		$definitions['customer.email'] = self::definition(
			'customer',
			esc_html__( 'Customer email', 'mail-doctor' ),
			esc_html__( 'Billing email address.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				$customer = self::customer( $context );
				if ( isset( $customer['email'] ) && '' !== $customer['email'] ) {
					return (string) $customer['email'];
				}

				$order = self::order( $context );
				if ( $order instanceof WC_Order ) {
					return (string) $order->get_billing_email();
				}

				return self::sample( $context, 'customer', 'email' );
			}
		);
		self::register_alias( $definitions, 'customer.email', 'customer_email' );

		$definitions['customer.company'] = self::definition(
			'customer',
			esc_html__( 'Customer company', 'mail-doctor' ),
			esc_html__( 'Billing company name.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				$customer = self::customer( $context );
				if ( isset( $customer['company'] ) && '' !== $customer['company'] ) {
					return (string) $customer['company'];
				}

				$order = self::order( $context );
				if ( $order instanceof WC_Order ) {
					return (string) $order->get_billing_company();
				}

				return self::sample( $context, 'customer', 'company' );
			}
		);
		self::register_alias( $definitions, 'customer.company', 'customer_company' );

		$definitions['customer.phone'] = self::definition(
			'customer',
			esc_html__( 'Customer phone', 'mail-doctor' ),
			esc_html__( 'Billing phone number.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				$customer = self::customer( $context );
				if ( isset( $customer['phone'] ) && '' !== $customer['phone'] ) {
					return (string) $customer['phone'];
				}

				$order = self::order( $context );
				if ( $order instanceof WC_Order ) {
					return (string) $order->get_billing_phone();
				}

				return self::sample( $context, 'customer', 'phone' );
			}
		);
		self::register_alias( $definitions, 'customer.phone', 'customer_phone' );

		$definitions['customer.note'] = self::definition(
			'customer',
			esc_html__( 'Customer note', 'mail-doctor' ),
			esc_html__( 'Latest note attached to the email context.', 'mail-doctor' ),
			'html',
			static function ( array $context ): string {
				return isset( $context['extra_tokens']['customer_note'] ) ? (string) $context['extra_tokens']['customer_note'] : '';
			}
		);
		self::register_alias( $definitions, 'customer.note', 'customer_note' );

		// Store tags.
		$definitions['store.name'] = self::definition(
			'store',
			esc_html__( 'Store name', 'mail-doctor' ),
			esc_html__( 'Site or store display name.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				$store = self::store( $context );
				if ( isset( $store['name'] ) && '' !== $store['name'] ) {
					return (string) $store['name'];
				}

				return get_bloginfo( 'name' );
			}
		);
		self::register_alias( $definitions, 'store.name', 'shop_name' );

		$definitions['store.url'] = self::definition(
			'store',
			esc_html__( 'Store URL', 'mail-doctor' ),
			esc_html__( 'Primary storefront URL.', 'mail-doctor' ),
			'url',
			static function ( array $context ): string {
				$store = self::store( $context );
				if ( isset( $store['url'] ) && '' !== $store['url'] ) {
					return (string) $store['url'];
				}

				return home_url( '/' );
			}
		);
		self::register_alias( $definitions, 'store.url', 'site_url' );
		self::register_alias( $definitions, 'store.url', 'store_url' );

		$definitions['store.email'] = self::definition(
			'store',
			esc_html__( 'Store email', 'mail-doctor' ),
			esc_html__( 'Default sender email address.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				$store = self::store( $context );
				if ( isset( $store['email'] ) && '' !== $store['email'] ) {
					return (string) $store['email'];
				}

				return get_option( 'woocommerce_email_from_address', get_option( 'admin_email' ) );
			}
		);

		$definitions['store.logo_url'] = self::definition(
			'store',
			esc_html__( 'Store logo URL', 'mail-doctor' ),
			esc_html__( 'Logo image URL used by WooCommerce emails.', 'mail-doctor' ),
			'url',
			static function ( array $context ): string {
				$store = self::store( $context );
				return isset( $store['logo_url'] ) ? (string) $store['logo_url'] : '';
			}
		);
		self::register_alias( $definitions, 'store.logo_url', 'shop_logo_url' );

		$definitions['store.logo_img'] = self::definition(
			'store',
			esc_html__( 'Store logo HTML', 'mail-doctor' ),
			esc_html__( 'HTML image element for the store logo.', 'mail-doctor' ),
			'html',
			static function ( array $context ): string {
				$store = self::store( $context );
				return isset( $store['logo_img'] ) ? (string) $store['logo_img'] : '';
			}
		);
		self::register_alias( $definitions, 'store.logo_img', 'shop_logo_img' );

		$definitions['store.brand'] = self::definition(
			'store',
			esc_html__( 'Store brand block', 'mail-doctor' ),
			esc_html__( 'Brand HTML combining logo or store name.', 'mail-doctor' ),
			'html',
			static function ( array $context ): string {
				$store = self::store( $context );
				return isset( $store['brand'] ) ? (string) $store['brand'] : '';
			}
		);
		self::register_alias( $definitions, 'store.brand', 'shop_brand' );

		// Email metadata tags.
		$definitions['email.id'] = self::definition(
			'email',
			esc_html__( 'Email identifier', 'mail-doctor' ),
			esc_html__( 'WooCommerce email ID.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				return isset( $context['email_id'] ) ? (string) $context['email_id'] : '';
			}
		);
		self::register_alias( $definitions, 'email.id', 'email_id' );

		$definitions['email.locale'] = self::definition(
			'email',
			esc_html__( 'Email locale', 'mail-doctor' ),
			esc_html__( 'Locale used for rendering.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				return isset( $context['locale'] ) ? (string) $context['locale'] : get_locale();
			}
		);

		$definitions['email.subject'] = self::definition(
			'email',
			esc_html__( 'Email subject', 'mail-doctor' ),
			esc_html__( 'Rendered subject line.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				return isset( $context['email_subject'] ) ? (string) $context['email_subject'] : '';
			}
		);
		self::register_alias( $definitions, 'email.subject', 'subject' );

		$definitions['email.heading'] = self::definition(
			'email',
			esc_html__( 'Email heading', 'mail-doctor' ),
			esc_html__( 'Rendered heading text.', 'mail-doctor' ),
			'text',
			static function ( array $context ): string {
				return isset( $context['email_heading'] ) ? (string) $context['email_heading'] : '';
			}
		);
		self::register_alias( $definitions, 'email.heading', 'heading' );

		// Account / misc tags.
		$definitions['account.reset_url'] = self::definition(
			'account',
			esc_html__( 'Password reset URL', 'mail-doctor' ),
			esc_html__( 'Account password reset link.', 'mail-doctor' ),
			'url',
			static function ( array $context ): string {
				return isset( $context['extra_tokens']['reset_url'] ) ? (string) $context['extra_tokens']['reset_url'] : '';
			}
		);
		self::register_alias( $definitions, 'account.reset_url', 'reset_url' );

		return $definitions;
	}

	/**
	 * Create a merge tag definition.
	 *
	 * @param string   $group       Definition group.
	 * @param string   $label       Display label.
	 * @param string   $description Description text.
	 * @param string   $sanitize    Sanitizer identifier.
	 * @param callable $callback    Resolver callback.
	 * @return array<string, mixed>
	 */
	protected static function definition( string $group, string $label, string $description, string $sanitize, callable $callback ): array {
		return array(
			'group'       => $group,
			'label'       => $label,
			'description' => $description,
			'sanitize'    => $sanitize,
			'callback'    => $callback,
		);
	}

	/**
	 * Ensure a merge tag identifier is safe for lookups.
	 *
	 * @param string $tag Merge tag identifier.
	 * @return string
	 */
	protected static function normalize_tag( string $tag ): string {
		$tag = strtolower( trim( $tag ) );

		if ( '' === $tag ) {
			return '';
		}

		if ( ! preg_match( '/^[a-z0-9\._\-]+$/', $tag ) ) {
			return '';
		}

		return $tag;
	}

	/**
	 * Sanitize a resolved merge tag value.
	 *
	 * @param string $tag       Merge tag identifier.
	 * @param mixed  $value     Raw value.
	 * @param string $sanitizer Sanitizer identifier.
	 * @return string
	 */
	protected static function sanitize_value( string $tag, $value, string $sanitizer ): string {
		if ( is_object( $value ) && ! method_exists( $value, '__toString' ) ) {
			return '';
		}

		if ( is_array( $value ) ) {
			return '';
		}

		$value = (string) $value;

		switch ( $sanitizer ) {
			case 'url':
				return esc_url_raw( $value );
			case 'html':
				return wp_kses_post( $value );
			case 'raw':
				return $value;
			case 'text':
			default:
				return sanitize_text_field( $value );
		}
	}

	/**
	 * Register an alias for an existing definition.
	 *
	 * @param array<string, array<string, mixed>> $definitions Definitions map.
	 * @param string                              $source      Master tag identifier.
	 * @param string                              $alias       Alias tag identifier.
	 * @param string|null                         $label       Optional override label.
	 */
	protected static function register_alias( array &$definitions, string $source, string $alias, string $label = null ): void {
		if ( ! isset( $definitions[ $source ] ) ) {
			return;
		}

		$definition = $definitions[ $source ];

		if ( null !== $label ) {
			$definition['label'] = $label;
		}

		$definition['alias_of'] = $source;
		$definitions[ $alias ]  = $definition;
	}

	/**
	 * Retrieve the order instance from context.
	 *
	 * @param array<string, mixed> $context Rendering context.
	 * @return WC_Order|null
	 */
	protected static function order( array $context ): ?WC_Order {
		return isset( $context['order'] ) && $context['order'] instanceof WC_Order
			? $context['order']
			: null;
	}

	/**
	 * Retrieve store metadata from context.
	 *
	 * @param array<string, mixed> $context Rendering context.
	 * @return array<string, string>
	 */
	protected static function store( array $context ): array {
		return isset( $context['store'] ) && is_array( $context['store'] )
			? $context['store']
			: array();
	}

	/**
	 * Retrieve customer metadata from context.
	 *
	 * @param array<string, mixed> $context Rendering context.
	 * @return array<string, string>
	 */
	protected static function customer( array $context ): array {
		return isset( $context['customer'] ) && is_array( $context['customer'] )
			? $context['customer']
			: array();
	}

	/**
	 * Fetch a legacy token or sample fallback.
	 *
	 * @param array<string, mixed> $context       Rendering context.
	 * @param string               $legacy_key   Legacy token key.
	 * @param string               $sample_group Sample context group.
	 * @param string               $sample_key   Sample value key.
	 * @return string
	 */
	protected static function legacy( array $context, string $legacy_key, string $sample_group = '', string $sample_key = '' ): string {
		if ( isset( $context['legacy_tokens'][ $legacy_key ] ) ) {
			return (string) $context['legacy_tokens'][ $legacy_key ];
		}

		if ( '' === $sample_group ) {
			return '';
		}

		return self::sample( $context, $sample_group, $sample_key ?: $legacy_key );
	}

	/**
	 * Fetch a sample value from preview context.
	 *
	 * @param array<string, mixed> $context Rendering context.
	 * @param string               $group   Sample group.
	 * @param string               $key     Value key.
	 * @return string
	 */
	protected static function sample( array $context, string $group, string $key ): string {
		if ( empty( $context['sample'] ) || ! is_array( $context['sample'] ) ) {
			return '';
		}

		$group_data = $context['sample'][ $group ] ?? array();

		if ( ! is_array( $group_data ) ) {
			return '';
		}

		return isset( $group_data[ $key ] ) ? (string) $group_data[ $key ] : '';
	}
}
