<?php
/**
 * Helper functions for ThemeREX Auto Dealer.
 *
 * @package ThemeREX\AutoDealer
 * @version 1.0.0
 */

declare(strict_types=1);

namespace ThemeREX\AutoDealer;

defined( 'ABSPATH' ) || exit;
/**
 * Fetch the vehicle specs row from the custom table.
 *
 * @deprecated Use {@see \ThemeREX\AutoDealer\Database\VehicleSpecsSync::get()} instead.
 *             This namespaced helper is preserved as a thin shim so callers
 *             keep working through one minor version; new code should call
 *             the canonical static method directly. (Plan T2-4.)
 *
 * @param int $postId The vehicle post ID.
 * @return array<string, mixed>|null Associative array of specs, or null if not found.
 */
function getVehicleSpecs( int $postId ): ?array {
	return \ThemeREX\AutoDealer\Database\VehicleSpecsSync::get( $postId );
}

/**
 * Format a price value with the configured currency symbol.
 *
 * @param float $price The price value.
 * @param bool  $html  Whether to return HTML markup or plain text.
 * @return string Formatted price string.
 */
function formatPrice( float $price, bool $html = true ): string {
	$symbol        = getSettings( 'currency_symbol', '$' );
	$position      = getSettings( 'currency_position', 'before' );
	$decimals      = (int) getSettings( 'price_decimals', 0 );
	$decimal_sep   = (string) getSettings( 'price_decimal_separator', '.' );
	$thousands_sep = (string) getSettings( 'price_thousands_separator', ',' );

	/**
	 * Filter the locale used for price formatting. i18n integrations
	 * (WpmlIntegration, PolylangIntegration) hook this to switch
	 * separators per language without requiring per-locale settings.
	 *
	 * @param array<string,string|int> $locale Decimal sep, thousands sep, decimals, symbol, position.
	 * @param float                    $price
	 */
	$locale = (array) apply_filters(
		'trx_ad_format_price_locale',
		array(
			'symbol'        => $symbol,
			'position'      => $position,
			'decimals'      => $decimals,
			'decimal_sep'   => $decimal_sep,
			'thousands_sep' => $thousands_sep,
		),
		$price
	);

	$formatted = number_format(
		$price,
		(int) ( $locale['decimals'] ?? $decimals ),
		(string) ( $locale['decimal_sep'] ?? $decimal_sep ),
		(string) ( $locale['thousands_sep'] ?? $thousands_sep )
	);

	$resolved_symbol   = (string) ( $locale['symbol'] ?? $symbol );
	$resolved_position = (string) ( $locale['position'] ?? $position );

	if ( 'after' === $resolved_position ) {
		$result = $formatted . $resolved_symbol;
	} else {
		$result = $resolved_symbol . $formatted;
	}

	if ( $html ) {
		return '<span class="trx-ad-price__value">' . esc_html( $result ) . '</span>';
	}

	return $result;
}

/**
 * Get a plugin setting from the trx_ad_settings option.
 *
 * @param string $key     Optional setting key. Empty returns all settings.
 * @param mixed  $default Default value if key not found.
 * @return mixed Setting value or full settings array.
 */
function getSettings( string $key = '', mixed $default = null ): mixed {
	$settings = get_option( 'trx_ad_settings', array() );

	if ( ! is_array( $settings ) ) {
		$settings = array();
	}

	if ( '' === $key ) {
		return $settings;
	}

	return $settings[ $key ] ?? $default;
}

/**
 * Get all dealer meta fields as an associative array.
 *
 * @param int $dealerId The dealer post ID.
 * @return array<string, mixed> Array of dealer meta fields.
 */
function getDealerMeta( int $dealerId ): array {
	// Map of `getDealerMeta()` array key → list of post-meta keys to try
	// in order. The admin UI (Admin\DealerMetaBox) writes under specific
	// names that don't always match the short labels we expose here:
	//
	// address → _trx_ad_street_address
	// hours   → _trx_ad_working_hours
	// lat/lng → _trx_ad_lat / _trx_ad_lng (admin field names)
	//
	// Legacy short keys (`_trx_ad_address`, `_trx_ad_hours`, etc.) and
	// no-underscore variants (`trx_ad_phone` from older populate scripts)
	// are kept as fallbacks so existing data stays readable.
	$key_map = array(
		'phone'     => array( '_trx_ad_phone', 'trx_ad_phone' ),
		'email'     => array( '_trx_ad_email', 'trx_ad_email' ),
		'whatsapp'  => array( '_trx_ad_whatsapp', 'trx_ad_whatsapp' ),
		'address'   => array( '_trx_ad_street_address', '_trx_ad_address', 'trx_ad_address' ),
		'city'      => array( '_trx_ad_city', 'trx_ad_city' ),
		'state'     => array( '_trx_ad_state', 'trx_ad_state' ),
		'zip'       => array( '_trx_ad_postcode', '_trx_ad_zip', 'trx_ad_zip' ),
		'country'   => array( '_trx_ad_country', 'trx_ad_country' ),
		'website'   => array( '_trx_ad_website', 'trx_ad_website' ),
		'hours'     => array( '_trx_ad_working_hours', '_trx_ad_hours', 'trx_ad_hours' ),
		'latitude'  => array( '_trx_ad_lat', '_trx_ad_latitude', 'trx_ad_latitude' ),
		'longitude' => array( '_trx_ad_lng', '_trx_ad_longitude', 'trx_ad_longitude' ),
	);

	$meta = array();
	foreach ( $key_map as $field => $keys ) {
		$value = '';
		foreach ( $keys as $key ) {
			$candidate = get_post_meta( $dealerId, $key, true );
			if ( '' !== $candidate && false !== $candidate ) {
				$value = $candidate;
				break;
			}
		}
		$meta[ $field ] = $value;
	}

	// Logo is the WP featured image (per DealerPostType — `featured_image`
	// label is "Dealer Logo"), NOT a custom meta field.
	$thumbnail_id = (int) get_post_thumbnail_id( $dealerId );
	$meta['logo'] = $thumbnail_id > 0 ? $thumbnail_id : 0;

	return $meta;
}

/**
 * Get the plugin templates directory path.
 *
 * @return string Absolute path to the templates directory (with trailing slash).
 */
function getPluginTemplatesPath(): string {
	return TRX_AD_PATH . 'templates/';
}

/**
 * SVG markup for the frontend action icons (favorite hearts, compare bars).
 *
 * Centralized so themes can swap the glyphs without overriding whole
 * templates (tester feedback #9). Escape at the echo site with
 * `wp_kses( ..., allowedSvgTags() )`.
 *
 * @param string $icon Icon key: 'favorite' or 'compare'.
 * @return string SVG markup ('' for unknown keys).
 */
function getActionIconSvg( string $icon ): string {
	$icons = array(
		'favorite' => '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg>',
		'compare'  => '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>',
	);

	$svg = $icons[ $icon ] ?? '';

	/**
	 * Filter an action-icon SVG so themes/child plugins can replace the glyph.
	 *
	 * @param string $svg  Default SVG markup.
	 * @param string $icon Icon key ('favorite', 'compare').
	 */
	return (string) apply_filters( 'trx_ad_action_icon_svg', $svg, $icon );
}

/**
 * Kses allowlist for inline SVG markup (taxonomy icons, QR previews,
 * play-button overlays). Used with `wp_kses()` at the echo site.
 *
 * @return array<string, array<string, bool>>
 */
function allowedSvgTags(): array {
	$common = array(
		'class'            => true,
		'id'               => true,
		'style'            => true,
		'fill'             => true,
		'fill-rule'        => true,
		'fill-opacity'     => true,
		'stroke'           => true,
		'stroke-width'     => true,
		'stroke-linecap'   => true,
		'stroke-linejoin'  => true,
		'stroke-dasharray' => true,
		'opacity'          => true,
		'transform'        => true,
		'aria-hidden'      => true,
		'role'             => true,
		'focusable'        => true,
	);

	return array(
		'svg'      => array_merge(
			$common,
			array(
				'xmlns'               => true,
				'width'               => true,
				'height'              => true,
				'viewbox'             => true,
				'preserveaspectratio' => true,
				'version'             => true,
			)
		),
		'g'        => $common,
		'defs'     => $common,
		'symbol'   => array_merge( $common, array( 'viewbox' => true ) ),
		'title'    => $common,
		'desc'     => $common,
		'path'     => array_merge( $common, array( 'd' => true ) ),
		'rect'     => array_merge(
			$common,
			array(
				'x'      => true,
				'y'      => true,
				'width'  => true,
				'height' => true,
				'rx'     => true,
				'ry'     => true,
			)
		),
		'circle'   => array_merge(
			$common,
			array(
				'cx' => true,
				'cy' => true,
				'r'  => true,
			)
		),
		'ellipse'  => array_merge(
			$common,
			array(
				'cx' => true,
				'cy' => true,
				'rx' => true,
				'ry' => true,
			)
		),
		'line'     => array_merge(
			$common,
			array(
				'x1' => true,
				'y1' => true,
				'x2' => true,
				'y2' => true,
			)
		),
		'polyline' => array_merge( $common, array( 'points' => true ) ),
		'polygon'  => array_merge( $common, array( 'points' => true ) ),
		'use'      => array_merge(
			$common,
			array(
				'href'       => true,
				'xlink:href' => true,
				'x'          => true,
				'y'          => true,
			)
		),
	);
}

/**
 * Kses allowlist for plugin-rendered HTML fragments: the `post` allowlist
 * extended with form controls (search/contact/listing forms), inline SVG
 * (icon set), iframes (video embeds), and `data-*`/`aria-*` attributes on
 * every element (Swiper/lightbox/interactivity hooks).
 *
 * Built once per request; used with `wp_kses()` at every `echo $html` site
 * (escape-late, per the wp.org 2026-07 review round 2).
 *
 * @return array<string, array<string, bool>>
 */
function allowedHtml(): array {
	static $allowed = null;

	if ( null === $allowed ) {
		$allowed = wp_kses_allowed_html( 'post' );

		$allowed['form']     = array(
			'action'       => true,
			'method'       => true,
			'class'        => true,
			'id'           => true,
			'enctype'      => true,
			'novalidate'   => true,
			'autocomplete' => true,
		);
		$allowed['input']    = array(
			'type'         => true,
			'name'         => true,
			'value'        => true,
			'class'        => true,
			'id'           => true,
			'placeholder'  => true,
			'required'     => true,
			'checked'      => true,
			'disabled'     => true,
			'readonly'     => true,
			'min'          => true,
			'max'          => true,
			'step'         => true,
			'minlength'    => true,
			'maxlength'    => true,
			'pattern'      => true,
			'size'         => true,
			'list'         => true,
			'multiple'     => true,
			'accept'       => true,
			'autocomplete' => true,
			'inputmode'    => true,
			'title'        => true,
			'style'        => true,
		);
		$allowed['select']   = array(
			'name'     => true,
			'class'    => true,
			'id'       => true,
			'required' => true,
			'disabled' => true,
			'multiple' => true,
			'size'     => true,
			'title'    => true,
			'style'    => true,
		);
		$allowed['option']   = array(
			'value'    => true,
			'selected' => true,
			'disabled' => true,
			'label'    => true,
		);
		$allowed['optgroup'] = array(
			'label'    => true,
			'disabled' => true,
		);
		$allowed['textarea'] = array(
			'name'        => true,
			'class'       => true,
			'id'          => true,
			'rows'        => true,
			'cols'        => true,
			'placeholder' => true,
			'required'    => true,
			'disabled'    => true,
			'readonly'    => true,
			'minlength'   => true,
			'maxlength'   => true,
			'style'       => true,
		);
		$allowed['button']   = array(
			'type'     => true,
			'name'     => true,
			'value'    => true,
			'class'    => true,
			'id'       => true,
			'disabled' => true,
			'title'    => true,
			'style'    => true,
		);
		$allowed['label']    = array(
			'for'   => true,
			'class' => true,
			'id'    => true,
			'style' => true,
		);
		$allowed['fieldset'] = array(
			'class'    => true,
			'id'       => true,
			'disabled' => true,
			'style'    => true,
		);
		$allowed['legend']   = array(
			'class' => true,
			'id'    => true,
		);
		$allowed['datalist'] = array(
			'id' => true,
		);
		$allowed['output']   = array(
			'for'   => true,
			'class' => true,
			'id'    => true,
		);
		$allowed['progress'] = array(
			'value' => true,
			'max'   => true,
			'class' => true,
			'id'    => true,
		);
		$allowed['iframe']   = array(
			'src'             => true,
			'width'           => true,
			'height'          => true,
			'frameborder'     => true,
			'allow'           => true,
			'allowfullscreen' => true,
			'loading'         => true,
			'referrerpolicy'  => true,
			'title'           => true,
			'class'           => true,
			'id'              => true,
			'style'           => true,
		);
		$allowed['picture']  = array(
			'class' => true,
			'id'    => true,
		);
		$allowed['source']   = array(
			'src'    => true,
			'srcset' => true,
			'sizes'  => true,
			'type'   => true,
			'media'  => true,
		);
		$allowed['template'] = array(
			'class' => true,
			'id'    => true,
		);
		// Layout builders (Elementor) print scoped <style> blocks in content.
		$allowed['style'] = array(
			'id'    => true,
			'class' => true,
			'media' => true,
			'type'  => true,
		);

		$allowed = array_merge( $allowed, allowedSvgTags() );

		// data-* / aria-* / tabindex on every element — Swiper config,
		// lightbox hooks, compare/favorite vehicle IDs, and
		// block-interactivity directives all travel via data attributes;
		// tabindex="-1" keeps honeypot fields and decorative links out of
		// the tab order (stripping it is an axe aria-hidden-focus violation,
		// caught by E2E spec 18 on 2026-07-20).
		foreach ( $allowed as $tag => $attrs ) {
			if ( is_array( $attrs ) ) {
				$attrs['data-*']   = true;
				$attrs['aria-*']   = true;
				$attrs['tabindex'] = true;
				$allowed[ $tag ]   = $attrs;
			}
		}
	}

	/**
	 * Filter the kses allowlist used for plugin-rendered HTML fragments.
	 *
	 * @param array<string, array<string, bool>> $allowed Kses allowlist.
	 */
	return (array) apply_filters( 'trx_ad_allowed_html', $allowed );
}
