<?php
/**
 * Utility functions for the plugin.
 *
 * This file is for custom helper functions.
 * These should not be confused with WordPress template
 * tags. Template tags typically use prefixing, as opposed
 * to Namespaces.
 *
 * @link https://developer.wordpress.org/themes/basics/template-tags/
 * @package StrategyBlocks
 */

namespace StrategyBlocks\Utility;

use StrategyBlocks\FontAwesomeSVG;

/**
 * Get asset info from extracted asset files
 *
 * @param string $slug Asset slug as defined in build/webpack configuration
 * @param string $attribute Optional attribute to get. Can be version or dependencies
 * @return string|array
 */
function get_asset_info( $slug, $attribute = null ) {
	if ( file_exists( STRATEGY_BLOCKS_PATH . 'dist/js/' . $slug . '.asset.php' ) ) {
		$asset = require STRATEGY_BLOCKS_PATH . 'dist/js/' . $slug . '.asset.php';
	} elseif ( file_exists( STRATEGY_BLOCKS_PATH . 'dist/css/' . $slug . '.asset.php' ) ) {
		$asset = require STRATEGY_BLOCKS_PATH . 'dist/css/' . $slug . '.asset.php';
	} else {
		return null;
	}

	if ( ! empty( $attribute ) && isset( $asset[ $attribute ] ) ) {
		return $asset[ $attribute ];
	}

	return $asset;
}

/**
 * The list of knows contexts for enqueuing scripts/styles.
 *
 * @return array
 */
function get_enqueue_contexts() {
	return [ 'admin', 'frontend', 'shared' ];
}

/**
 * Generate an URL to a stylesheet, taking into account whether SCRIPT_DEBUG is enabled.
 *
 * @param string $stylesheet Stylesheet file name (no .css extension)
 * @param string $context Context for the script ('admin', 'frontend', or 'shared')
 *
 * @return string URL
 */
function style_url( $stylesheet, $context ) {

	if ( ! in_array( $context, get_enqueue_contexts(), true ) ) {
		return new WP_Error( 'invalid_enqueue_context', 'Invalid $context specified in StrategyBlocks stylesheet loader.' );
	}

	return STRATEGY_BLOCKS_URL . "dist/css/{$stylesheet}.css";
}

/**
 * Generate an URL to a script, taking into account whether SCRIPT_DEBUG is enabled.
 *
 * @param string $script Script file name (no .js extension)
 * @param string $context Context for the script ('admin', 'frontend', or 'shared')
 *
 * @return string|WP_Error URL
 */
function script_url( $script, $context ) {

	if ( ! in_array( $context, get_enqueue_contexts(), true ) ) {
		return new WP_Error( 'invalid_enqueue_context', 'Invalid $context specified in StrategyBlocks script loader.' );
	}

	return STRATEGY_BLOCKS_URL . "dist/js/{$script}.js";
}

/**
 * Retrieve and load a template part from within the plugin,
 * allowing for optional arguments.
 *
 * @param string $slug The slug name for the generic template.
 * @param string $name Optional. The name of the specialized template.
 * @param array  $args Optional. Associative array of variables to pass into the template.
 */
function get_plugin_template_part( $slug, $name = null, $args = array() ) {
	// Build possible template filenames.
	$templates = array();
	if ( $name ) {
		$templates[] = "{$slug}-{$name}.php";
	}
	$templates[] = "{$slug}.php";

	foreach ( $templates as $file ) {
		$possible_path = STRATEGY_BLOCKS_INC . $file;
		if ( file_exists( $possible_path ) ) {
			$template = $possible_path;
			break;
		}
	}

	// If not found in the plugin, fall back to the theme directory.
	if ( ! $template ) {
		// Try to locate the template in the theme (or parent theme).
		$template = locate_template( $templates, false );
	}

	// If a valid template file is found, load it.
	if ( $template ) {
		load_template( $template, false, $args );
	}
}

/**
 * Outputs the sanitized contents of an SVG that is passed with it's URL.
 *
 * @param string  $svg                  The SVG markup <svg...
 * @param boolean $output               Whether to output the markup or return it as a string
 * @param string  $display_type 'decorative' (default) or 'informational' for accessibility.
 */
function sanitize_and_output_svg( $svg, $output = true, $display_type = 'decorative' ) {
	$kses_defaults = wp_kses_allowed_html( 'post' );
	$svg_args = [
		'svg' => [
			'class' => true,
			'aria-hidden' => true,
			'aria-label' => true,
			'aria-labelledby' => true,
			'role' => true,
			'xmlns' => true,
			'width' => true,
			'height' => true,
			'viewbox' => true,
			'viewBox' => true,
			'preserveaspectratio' => true,
			'fill' => true,
			'stroke' => true,
			'focusable' => true,
			// 'style' => true,
			'id' => true,
			'xmlns:xlink' => true,
		],
		'g' => [
			'fill' => true,
			'transform' => true,
			'stroke' => true,
			'opacity' => true,
			'class' => true,
			'style' => true,
		],
		'title' => [ 'title' => true ],
		'path' => [
			'd' => true,
			'fill' => true,
			'stroke' => true,
			'stroke-width' => true,
			'class' => true,
			'style' => true,
			'transform' => true,
			'id' => true,
		],
		'rect' => [
			'x' => true,
			'y' => true,
			'width' => true,
			'height' => true,
			'rx' => true,
			'ry' => true,
			'fill' => true,
			'stroke' => true,
			'class' => true,
			'style' => true,
			'transform' => true,
		],
		'circle' => [
			'cx' => true,
			'cy' => true,
			'r' => true,
			'fill' => true,
			'stroke' => true,
			'class' => true,
			'style' => true,
		],
		'ellipse' => [
			'cx' => true,
			'cy' => true,
			'rx' => true,
			'ry' => true,
			'fill' => true,
			'stroke' => true,
			'class' => true,
			'style' => true,
		],
		'line' => [
			'x1' => true,
			'y1' => true,
			'x2' => true,
			'y2' => true,
			'stroke' => true,
			'stroke-width' => true,
			'class' => true,
			'style' => true,
		],
		'polygon' => [
			'points' => true,
			'fill' => true,
			'stroke' => true,
			'class' => true,
			'style' => true,
		],
		'polyline' => [
			'points' => true,
			'fill' => true,
			'stroke' => true,
			'class' => true,
			'style' => true,
		],
		'text' => [
			'x' => true,
			'y' => true,
			'dx' => true,
			'dy' => true,
			'text-anchor' => true,
			'fill' => true,
			'stroke' => true,
			'class' => true,
			'style' => true,
			'transform' => true,
		],
		'tspan' => [
			'x' => true,
			'y' => true,
			'dx' => true,
			'dy' => true,
			'fill' => true,
			'stroke' => true,
			'class' => true,
			'style' => true,
		],
		'defs' => [],
		'clippath' => [ 'id' => true ],
		'desc' => [],
		'use' => [
			'href' => true,
			'xlink:href' => true,
			'width' => true,
			'height' => true,
		],
		'style' => [],
		'lineargradient' => [
			'id' => true,
			'x1' => true,
			'y1' => true,
			'x2' => true,
			'y2' => true,
			'gradienttransform' => true,
			'gradientunits' => true,
		],
		'stop' => [
			'offset' => true,
			'stop-color' => true,
		],
	];

	$allowed_svg_tags = array_merge( $kses_defaults, $svg_args );
	if ( isset( $svg ) ) {
		if ( 'decorative' === $display_type ) {
			$svg = preg_replace(
				'/<svg([^>]+)>/',
				'<svg$1 aria-hidden="true" focusable="false">',
				$svg
			);
		}
		if ( $output ) {
			echo wp_kses( $svg, $allowed_svg_tags );
		} else {
			return wp_kses( $svg, $allowed_svg_tags );
		}
	} else {
		return wp_kses( '', $allowed_svg_tags );
	}
}

/**
 * Retrieves an SVG file from a given URL, processes it, and formats it with specified attributes.
 *
 * This function attempts to retrieve the SVG file from the local uploads directory if available.
 * Otherwise, it fetches the file using cURL. It also extracts width and height from the SVG if
 * they are not provided, and ensures the SVG tag is properly formatted with the given classes,
 * width, and height.
 *
 * @param string      $svg_url  The URL of the SVG file.
 * @param string      $classes  The CSS classes to be added to the SVG.
 * @param int|null    $height   (Optional) The height of the SVG. Extracted from SVG if not provided.
 * @param int|null    $width    (Optional) The width of the SVG. Extracted from SVG if not provided.
 * @param string      $display_type 'decorative' (default) or 'informational' for accessibility.
 * @param string|null $label        Optional label for informational icons (aria-label content).
 * @param int|null    $media_id     Optional media ID (for uploaded icons).
 *
 * @return string The formatted SVG content with the specified attributes.
 */
function get_svg_by_url_and_format_svg( $svg_url, $classes, $height = null, $width = null, $display_type = 'decorative', $label = null, $media_id = null ) {
	$uploads = wp_upload_dir();
	$svg = '';

	$upload_baseurl = parse_url( $uploads['baseurl'], PHP_URL_HOST );
	$svg_host = parse_url( $svg_url, PHP_URL_HOST );

	// Only try to load locally if host matches your upload base
	if ( $upload_baseurl && $svg_host && $upload_baseurl === $svg_host && strpos( $svg_url, 'uploads' ) !== false ) {
		$upload_path = strstr( strstr( $svg_url, 'uploads' ), '/' );
		$file_path = trailingslashit( $uploads['basedir'] ) . ltrim( $upload_path, '/' );

		if ( file_exists( $file_path ) ) {
			$svg = file_get_contents( $file_path );
		}
	}

	// If we still don't have it, use cURL fetch
	if ( empty( $svg ) ) {
		$svg = file_get_contents_curl( $svg_url );
	}

	// Extract inner SVG content
	if ( ! preg_match( '/<svg\b([^>]*)>(.*?)<\/svg>/is', $svg, $matches ) ) {
		return $svg; // fallback
	}
	$original_attrs = $matches[1];
	$svg_inner = $matches[2];

	// Extract viewBox, xmlns, and other preserved attributes
	preg_match_all( '/([\w:-]+)=["\']([^"\']*)["\']/', $original_attrs, $attr_matches, PREG_SET_ORDER );

	$attrs = [];
	foreach ( $attr_matches as $match ) {
		$key = strtolower( $match[1] );
		// Skip controlled attributes
		if ( in_array( $key, [ 'width', 'height', 'class', 'aria-hidden', 'aria-label', 'role', 'focusable' ], true ) ) {
			continue;
		}
		$attrs[] = sprintf( '%s="%s"', $key, esc_attr( $match[2] ) );
	}

	// Set width and height
	$width = $width ?? ( preg_match( '/\bwidth=["\']([\d.]+)["\']/', $original_attrs, $m ) ? $m[1] : '100%' );
	$height = $height ?? ( preg_match( '/\bheight=["\']([\d.]+)["\']/', $original_attrs, $m ) ? $m[1] : '100%' );

	// Set accessibility attributes
	if ( 'informational' === $display_type ) {
		if ( $media_id ) {
			$title = get_the_title( $media_id );
			$label = ! empty( $title ) ? $title : 'Informational icon';
		}
		$label = $label ?? 'Informational icon';
		$attrs[] = sprintf( 'role="img" aria-label="%s"', esc_attr( $label ) );
	} else {
		$attrs[] = 'aria-hidden="true"';
		$attrs[] = 'focusable="false"';
	}

	// Add controlled attributes
	$attrs[] = sprintf( 'width="%s"', esc_attr( $width ) );
	$attrs[] = sprintf( 'height="%s"', esc_attr( $height ) );
	$attrs[] = sprintf( 'class="%s"', esc_attr( $classes ) );

	// Assemble
	return sprintf( '<svg %s>%s</svg>', implode( ' ', $attrs ), $svg_inner );
}

/**
 * Retrieves the contents of a file via curl. If on a dev environment, unsecure option is allowed.
 *
 * @throws Exception            Exception thrown in case of curl error
 *
 * @param string $url           The url of the file to be retrieved
 * @param string $ua            The user agent that CURL should use to grab the file.
 * @param string $referer       The url to set as the referring url.
 *
 * @return string               The contents of the file
 */
function file_get_contents_curl( $url, $ua = 'Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1', $referer = '' ) {
	if ( empty( $referer ) ) {
		$referer = get_home_url();
	}

	try {
		$ch = curl_init();

		curl_setopt( $ch, CURLOPT_AUTOREFERER, true );
		curl_setopt( $ch, CURLOPT_HEADER, 0 );
		curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
		curl_setopt( $ch, CURLOPT_URL, $url );
		curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
		curl_setopt( $ch, CURLOPT_USERAGENT, $ua );
		curl_setopt( $ch, CURLOPT_REFERER, $referer );
		if ( 'development' === WP_ENV ) {
			curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false );
		}

		$data = curl_exec( $ch );
		curl_close( $ch );

		if ( false === $data ) {
			throw new Exception( curl_error( $ch ), curl_errno( $ch ) );
		}
	} catch ( Exception $e ) {
		trigger_error(
			sprintf(
				'Curl failed with error #%d: %s',
				esc_attr( $e->getCode() ),
				esc_attr( $e->getMessage() )
			),
			E_USER_ERROR
		);
	} finally {
		if ( is_resource( $ch ) ) {
			curl_close( $ch );
		}
	}

	return $data;
}

/**
 * Outputs sanitized HTML to the page.
 *
 * @param string $content               The HTML content to be sanitized
 */
function sanitize_and_output_html_content( $content ) {
	if ( isset( $content ) ) {
		$kses_defaults = wp_kses_allowed_html( 'post' );
		echo do_shortcode( wp_kses( $content, $kses_defaults ) );
	}
}

/**
 * Creates css classes for blocks such as margin, padding, blockGap, and others.
 *
 * @param array $attributes    The $attributes object.
 *
 * @return string $classes A string of spaced delimited classes to add to block classes.
 */
function get_block_style_classes( $attributes ) {
	$classes = '';
	if ( ! isset( $attributes['style'] ) ) {
		return $classes;
	}
	$style = $attributes['style'];
	if ( isset( $style['spacing'] ) ) {
		$classes .= transform_block_style_dimensions_to_class( $attributes, 'blockGap' );
		$classes .= transform_block_style_dimensions_to_class( $attributes, 'padding' );
		$classes .= transform_block_style_dimensions_to_class( $attributes, 'margin' );
	}

	return $classes;
}

/**
 * Transforms the 2d array of block style dimensions to a single class.
 *
 * @param string $attributes    The $attributes.
 * @param string $property    The property key from $attributes['style']['spacing'][$property] array.
 *
 * @return string $classes A string of spaced delimited classes to add to block classes.
 */
function transform_block_style_dimensions_to_class( $attributes, $property ) {
	$classes = '';
	if ( ! isset( $attributes['style'] ) ) {
		return $classes;
	}
	$style = $attributes['style'];
	if ( isset( $style['spacing'][ $property ] ) ) {
		$dimensional_property = $style['spacing'][ $property ];
		$shorthand = substr( $property, 0, 1 );
		if ( 'blockGap' === $property ) {
			$shorthand = 'g';
		}
		if ( ! is_array( $dimensional_property ) ) {
			$dimensional_property = [ $dimensional_property ];
		}
		foreach ( $dimensional_property as $dimension => $value ) {
			$setting = explode( '|', $value );
			if ( is_array( $setting ) && count( $setting ) > 1 ) {
				$value = $setting[2];
			} else {
				$value = $setting[0];
			}
			$prefix = 0 !== $dimension ? substr( $dimension, 0, 1 ) : '';
			$classes .= ' ' . $shorthand . $prefix . '-' . $value;
		}
	}

	return $classes;
}

/**
 * Creates css classes for blocks from native WordPress key/values such as: alignment, font-size, etc.
 *
 * @param array $attributes    The $attributes object.
 *
 * @return string $classes A string of spaced delimited classes to add to block classes.
 */
function get_native_wp_classes( $attributes ) {
	$classes = '';
	if ( ! empty( $attributes['className'] ) ) {
		$classes .= ' ' . $attributes['className'];
	}
	if ( ! empty( $attributes['align'] ) ) {
		$classes .= ' align-' . $attributes['align'];
	}
	if ( isset( $attributes['style']['typography']['textAlign'] ) && $attributes['style']['typography']['textAlign'] ) {
		$classes .= ' text-' . $attributes['style']['typography']['textAlign'];
	}
	if ( ! empty( $attributes['fontSize'] ) ) {
		$classes .= ' size-' . $attributes['fontSize'];
	}
	if ( isset( $attributes['full_height'] ) && $attributes['full_height'] ) {
		$classes .= ' h-full';
	}
	if ( isset( $attributes['textColor'] ) && $attributes['textColor'] ) {
		$classes .= ' text-color-' . $attributes['textColor'];
	}
	if ( isset( $attributes['backgroundColor'] ) && $attributes['backgroundColor'] ) {
		$classes .= ' bg-color-' . $attributes['backgroundColor'];
	}
	if ( isset( $attributes['style']['elements']['link']['color']['text'] ) && $attributes['style']['elements']['link']['color']['text'] ) {
		$link_css_vars = explode( '|', $attributes['style']['elements']['link']['color']['text'] );
		$classes .= ' link-color-' . array_pop( $link_css_vars );
	}
	if ( isset( $attributes['style']['elements']['link'][':hover']['color']['text'] ) && $attributes['style']['elements']['link'][':hover']['color']['text'] ) {
		$link_hover_css_vars = explode( '|', $attributes['style']['elements']['link'][':hover']['color']['text'] );
		$classes .= ' link-hover-color-' . array_pop( $link_hover_css_vars );
	}

	return $classes;
}

/**
 * Get size information for all currently-registered image sizes.
 *
 * @global $_wp_additional_image_sizes
 * @uses   get_intermediate_image_sizes()
 * @return array $sizes Data for all currently-registered image sizes.
 */
function get_image_sizes() {
	global $_wp_additional_image_sizes;

	$sizes = array();

	foreach ( get_intermediate_image_sizes() as $_size ) {
		if ( in_array( $_size, array( 'thumbnail', 'medium', 'medium_large', 'large' ) ) ) {
			$sizes[ $_size ]['width'] = get_option( "{$_size}_size_w" );
			$sizes[ $_size ]['height'] = get_option( "{$_size}_size_h" );
			$sizes[ $_size ]['crop'] = (bool) get_option( "{$_size}_crop" );
		} elseif ( isset( $_wp_additional_image_sizes[ $_size ] ) ) {
			$sizes[ $_size ] = array(
				'width' => $_wp_additional_image_sizes[ $_size ]['width'],
				'height' => $_wp_additional_image_sizes[ $_size ]['height'],
				'crop' => $_wp_additional_image_sizes[ $_size ]['crop'],
			);
		}
	}

	return $sizes;
}

/**
 * Get size information for a specific image size.
 *
 * @uses   get_image_sizes()
 * @param  string $size The image size for which to retrieve data.
 * @return bool|array $size Size data about an image size or false if the size doesn't exist.
 */
function get_image_size( $size ) {
	$sizes = get_image_sizes();

	if ( isset( $sizes[ $size ] ) ) {
		return $sizes[ $size ];
	}

	return false;
}

/**
 * Get the width of a specific image size.
 *
 * @uses   get_image_size()
 * @param  string $size The image size for which to retrieve data.
 * @return bool|string $size Width of an image size or false if the size doesn't exist.
 */
function get_image_width( $size ) {
	$size = get_image_size( $size );
	if ( ! $size ) {
		return false;
	}
	if ( isset( $size['width'] ) ) {
		return $size['width'];
	}

	return false;
}

/**
 * Get the height of a specific image size.
 *
 * @uses   get_image_size()
 * @param  string $size The image size for which to retrieve data.
 * @return bool|string $size Height of an image size or false if the size doesn't exist.
 */
function get_image_height( $size ) {
	$size = get_image_size( $size );
	if ( ! $size ) {
		return false;
	}

	if ( isset( $size['height'] ) ) {
		return $size['height'];
	}

	return false;
}

/**
 * Get the custom placeholder dimensions if set, otherwise returns the default dimensions.
 *
 * @param array|null $placeholder_dims Associative array potentially containing 'placeholder_width' and 'placeholder_height'.
 * @param string     $default A string representing the default dimensions to use if custom dimensions are not set.
 * @return string The dimensions of the placeholder in 'width x height' format.
 */
function get_placeholder_dimensions( $placeholder_dims, $default ) {
	return isset( $placeholder_dims['placeholder_width'], $placeholder_dims['placeholder_height'] )
		? $placeholder_dims['placeholder_width'] . 'x' . $placeholder_dims['placeholder_height']
		: $default;
}

/**
 * Extract colors from a CSS or Sass file
 *
 * @param string $path the path to your CSS variables file
 */
function get_colors( $path ) {

	$dir = get_stylesheet_directory();

	if ( file_exists( $dir . $path ) ) {
		$css_vars = file_get_contents( $dir . $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions
		// HEX(A) | RGB(A) | HSL(A) - rgba & hsla alpha as decimal or percentage
		// https://regex101.com/r/l7AZ8R/
		// this is a loose match and will accept almost anything within () for rgb(a) & hsl(a)
		// a more optinionated solution is WIP here if you can improve on it https://regex101.com/r/FEtzDu/
		preg_match_all( '(#(?:[\da-f]{3}){1}\b|#(?:[\da-f]{2}){3,4}\b|(rgb|hsl)a?\((\s|\d|[a-zA-Z]+|,|-|%|\.|\/)+\))', $css_vars, $matches );

		return $matches[0];
	}
}

/**
 * Adjust the brightness of a color (HEX)
 *
 * @param string $hex The hex code for the color
 * @param number $steps amount you want to change the brightness
 * @return string new color with brightness adjusted
 */
function adjust_brightness( $hex, $steps ) {

	// Steps should be between -255 and 255. Negative = darker, positive = lighter
	$steps = max( -255, min( 255, $steps ) );

	// Normalize into a six character long hex string
	$hex = str_replace( '#', '', $hex );
	if ( 3 === strlen( $hex ) ) {
		$hex = str_repeat( substr( $hex, 0, 1 ), 2 ) . str_repeat( substr( $hex, 1, 1 ), 2 ) . str_repeat( substr( $hex, 2, 1 ), 2 );
	}

	// Split into three parts: R, G and B
	$color_parts = str_split( $hex, 2 );
	$return = '#';

	foreach ( $color_parts as $color ) {
		$color = hexdec( $color ); // Convert to decimal
		$color = max( 0, min( 255, $color + $steps ) ); // Adjust color
		$return .= str_pad( dechex( $color ), 2, '0', STR_PAD_LEFT ); // Make two char hex code
	}

	return $return;
}

/**
 * Creates css classes for section container.
 *
 * @param array $attributes    The $attributes object.
 *
 * @return string $classes A string of spaced delimited classes to add to section container classes.
 */
function get_section_container_classes( $attributes ) {
	$classes = 'container';

	$content_width = $attributes['contentWidth'] ?? 'md';
	$custom_container_class = $attributes['containerClass'] ?? '';

	if ( $content_width ) {
		$classes .= ' container-' . $content_width;
	}
	if ( $custom_container_class ) {
		$classes .= ' ' . $custom_container_class;
	}

	return $classes;
}

/**
 * Creates css classes for row alignment.
 *
 * @param array $attributes    The $block object.
 *
 * @return string $classes A string of spaced delimited classes to add to row classes.
 */
function get_alignment_and_sizing_classes( $attributes ) {
	$classes = '';
	$xs_cascade = '';
	$value_buffer = '';

	$breakpoints = [ 'xl', 'lg', 'md', 'sm', 'xs' ];
	$device_map = [
		'xl' => 'desktop',
		'lg' => 'laptop',
		'md' => 'large-tablet',
		'sm' => 'tablet',
		'xs' => 'mobile',
	];

	foreach ( $breakpoints as $breakpoint ) {
		$device = $device_map[ $breakpoint ];

		$vertical = $attributes['verticalAlign'][ $device ] ?? '';
		$horizontal = $attributes['horizontalAlign'][ $device ] ?? '';
		$column = $attributes['columnWidth'][ $device ] ?? '';

		$customized = '' !== $vertical || '' !== $horizontal || '' !== $column;

		if ( $customized ) {
			$classes .= $value_buffer;
			$value_buffer = '';
			$xs_cascade = '';
		}

		if ( '' !== $vertical ) {
			$value_buffer .= " {$vertical}-{$breakpoint}";
			$xs_cascade .= " {$vertical}-xs";
		}
		if ( '' !== $horizontal ) {
			$value_buffer .= " {$horizontal}-{$breakpoint}";
			$xs_cascade .= " {$horizontal}-xs";
		}
		if ( '' !== $column ) {
			if ( 'full' === $column || 'auto' === $column ) {
				$value_buffer .= " col-{$column}-{$breakpoint}";
				$xs_cascade .= " col-{$column}-xs";
			} else {
				$value_buffer .= " col-{$breakpoint}-{$column}";
				$xs_cascade .= " col-xs-{$column}";
			}
		}
	}

	$classes .= $xs_cascade;

	return $classes;
}

/**
 * Checks if breakpoint specific property exists to make it easier to do conditional logic.
 *
 * @param array  $breakpoint    The $breakpoint array.
 * @param string $property    The $property key on the $breakpoint array.
 *
 * @return bool Whether or not that property is set and not empty.
 */
function breakpoint_property_isset( $breakpoint, $property ) {
	return isset( $breakpoint[ $property ] ) && ! empty( $breakpoint[ $property ] );
}

/**
 * Creates css classes for visibility.
 *
 * @param array $attributes    The $attributes object.
 *
 * @return string $classes A string of spaced delimited classes to add.
 */
function get_visibility_classes( $attributes ) {
	$classes = [];

	if ( ! empty( $attributes['visibilityHide'] ) ) {
		foreach ( $attributes['visibilityHide'] as $breakpoint ) {
			$classes[] = 'hide-' . $breakpoint;
		}
	}

	if ( ! empty( $attributes['visibilityShow'] ) ) {
		foreach ( $attributes['visibilityShow'] as $breakpoint ) {
			$classes[] = 'show-' . $breakpoint;
		}
	}

	return ! empty( $classes ) ? ' ' . implode( ' ', $classes ) : '';
}

/**
 * Normalizes a background position value into a structured array or CSS-compatible string.
 *
 * Accepts a string like "left top", "50% 50%", or an associative array with 'x' and 'y' keys.
 * Values are clamped between 0 and 1, representing percentages (e.g., 0.5 is 50%).
 * When $as_string is true, the function returns a string like "center top" or "50% 50%".
 *
 * @param string|array $position  The background position. Can be a CSS string (e.g., 'left top')
 *                                or an associative array with 'x' and 'y' numeric values.
 * @param bool         $as_string Optional. Whether to return the result as a CSS string.
 *                                Defaults to false.
 *
 * @return array|string Normalized background position, either as an array with 'x' and 'y' keys
 *                      (floats from 0 to 1) or as a CSS-compatible string.
 */
function normalize_background_position( $position, $as_string = false ) {
	$default = array(
		'x' => 0.5,
		'y' => 0.5,
	);

	$keyword_map = array(
		'0' => 'left',
		'0.0' => 'left',
		'0.5' => 'center', //phpcs:ignore Universal.Arrays.DuplicateArrayKey.Found
		'1.0' => 'right',
	);

	$keyword_map_y = array(
		'0' => 'left',
		'0.0' => 'top',
		'0.5' => 'center', //phpcs:ignore Universal.Arrays.DuplicateArrayKey.Found
		'1.0' => 'bottom',
	);

	// Step 1: Normalize to object
	if (
		is_array( $position ) &&
		isset( $position['x'], $position['y'] ) &&
		is_numeric( $position['x'] ) &&
		is_numeric( $position['y'] )
	) {
		$normalized = array(
			'x' => max( 0, min( 1, floatval( $position['x'] ) ) ),
			'y' => max( 0, min( 1, floatval( $position['y'] ) ) ),
		);
	} elseif ( is_string( $position ) && ! empty( trim( $position ) ) ) {
		$parts = preg_split( '/\s+/', trim( $position ) );
		if ( count( $parts ) < 2 ) {
			$normalized = $default;
		} else {
			list( $x_raw, $y_raw ) = $parts;

			$convert = function ( $val ) {
				$keywords = array(
					'left' => 0.0,
					'center' => 0.5,
					'right' => 1.0,
					'top' => 0.0,
					'bottom' => 1.0,
				);
				if ( isset( $keywords[ $val ] ) ) {
					return $keywords[ $val ];
				}
				if ( strpos( $val, '%' ) !== false ) {
					$num = floatval( str_replace( '%', '', $val ) );
					return max( 0, min( 1, $num / 100 ) );
				}
				return 0.5;
			};

			$normalized = array(
				'x' => $convert( $x_raw ),
				'y' => $convert( $y_raw ),
			);
		}
	} else {
		$normalized = $default;
	}

	// Step 2: Return as object or CSS string
	if ( ! $as_string ) {
		return $normalized;
	}

	$x = $normalized['x'];
	$y = $normalized['y'];

	$x_str = maybe_keyword( $x, $keyword_map );
	$y_str = maybe_keyword( $y, $keyword_map_y );

	return "{$x_str} {$y_str}";
}

/**
 * Converts a normalized float value into a keyword (like 'left', 'center', 'right'),
 * if it is close enough to a predefined key; otherwise returns a percentage string.
 *
 * @param float $value The normalized value (from 0 to 1).
 * @param array $map   Associative array mapping float values to keyword strings.
 *
 * @return string A keyword string (e.g., 'left', 'center', 'right') if matched,
 *                or a percentage string like '50%'.
 */
function maybe_keyword( $value, $map ) {
	foreach ( $map as $key => $label ) {
		if ( abs( $value - $key ) < 0.01 ) {
			return $label;
		}
	}
	return round( $value * 100 ) . '%';
}

/**
 * Converts a label into a slug, similar to how acf does it.
 *
 * @param string $label                 The string being slugified
 *
 * @return string                       The slugified string
 */
function slugify_acf_label( $label ) {
	return str_replace( '-', '_', strtolower( sanitize_title( $label ) ) );
}

/**
 * Calculate the reading time of a post.
 *
 * Gets the post content, counts the images, strips shortcodes, and strips tags.
 * Then counts the words. Converts images into a word count. And outputs the
 * total reading time.
 *
 * @link https://www.sciencedirect.com/science/article/abs/pii/S0749596X19300786#ab010
 *
 * @param int $post_id The Post ID.
 * @param int $wpm Words per minute
 * @return string|int The total reading time for the article or string if it's 0.
 *
 * TODO: This will need to be changed for native blocks.
 */
function calculate_reading_time( $post_id, $wpm = 250 ) {

	$content = get_post_field( 'post_content', $post_id );
	$number_of_images = substr_count( strtolower( $content ), '<img ' );

	$blocks = parse_blocks( $content );
	$acf_blocks = find_from_blocks( $blocks, [ 'acf/text-editor', 'acf/media' ] );

	foreach ( $acf_blocks as $block ) {
		if ( 'acf/media' === $block['blockName'] ) {
			$number_of_images++;
		}
		if ( 'acf/text-editor' === $block['blockName'] ) {
			$content .= $block['attrs']['data']['content'];
		}
	}

	if ( ! isset( $rt_options['include_shortcodes'] ) ) {
		$content = strip_shortcodes( $content );
	}

	$content = wp_strip_all_tags( $content );
	$word_count = count( preg_split( '/\s+/', $content ) );

	// Calculate additional time added to post by images.
	$additional_words_for_images = calculate_images( $number_of_images, $wpm );
	$word_count += $additional_words_for_images;

	$word_count = apply_filters( 'rtwp_filter_wordcount', $word_count );

	$reading_time = $word_count / $wpm;

	// If the reading time is 0 then return it as < 1 instead of 0.
	if ( 1 > $reading_time ) {
		$reading_time = __( '< 1', 'reading-time-wp' );
	} else {
		$reading_time = ceil( $reading_time );
	}

	return $reading_time;
}

/**
 * Adds additional reading time for images
 *
 * Calculate additional reading time added by images in posts. Based on calculations by Medium. https://blog.medium.com/read-time-and-you-bc2048ab620c
 *
 * @param int   $total_images number of images in post.
 * @param array $wpm words per minute.
 * @return int  Additional time added to the reading time by images.
 */
function calculate_images( $total_images, $wpm ) {
	$additional_time = 0;
	// For the first image add 12 seconds, second image add 11, ..., for image 10+ add 3 seconds.
	for ( $i = 1; $i <= $total_images; $i++ ) {
		if ( $i >= 10 ) {
			$additional_time += 3 * (int) $wpm / 60;
		} else {
			$additional_time += ( 12 - ( $i - 1 ) ) * (int) $wpm / 60;
		}
	}

	$additional_time = apply_filters( 'lg_additional_time', $total_images, $wpm );

	return $additional_time;
}

/**
 * Output the proper postfix for the reading time.
 *
 * @param string|int $time The total reading time for the article or string if it's 0.
 * @param string     $singular The postfix singular label.
 * @param string     $multiple The postfix label.
 *
 * @return string $postfix The calculated postfix.
 */
function reading_time_postfix( $time, $singular, $multiple ) {
	if ( $time > 1 ) {
		$postfix = $multiple;
	} else {
		$postfix = $singular;
	}

	$postfix = apply_filters( 'lg_reading_time_postfix', $postfix, $time, $singular, $multiple );

	return $postfix;
}

/**
 * Outputs the formatted reading time of the given post.
 *
 * @param int $post_id The Post ID.
 * @param int $wpm Words per minute
 *
 * @return string $printed_reading_time The formatted reading time.
 */
function get_reading_time( $post_id, $wpm ) {
	$reading_time = calculate_reading_time( $post_id, $wpm );
	$postfix = reading_time_postfix( $reading_time, 'minute read', 'minute read' );

	return $reading_time . ' ' . $postfix;
}

/**
 * Gets the excerpt for the post with a given custom length.
 *
 * @param int $excerpt_length Excerpt length by words
 *
 * @return string $excerpt The modified length excerpt.
 */
function get_custom_excerpt( $excerpt_length = 40 ) {
	$excerpt_length = has_excerpt() ? PHP_INT_MAX : $excerpt_length;
	$excerpt = explode( ' ', get_the_excerpt(), $excerpt_length );

	if ( count( $excerpt ) >= $excerpt_length ) {
		array_pop( $excerpt );
		$excerpt = implode( ' ', $excerpt ) . '...';
	} else {
		$excerpt = implode( ' ', $excerpt );
	}

	$excerpt = preg_replace( '`[[^]]*]`', '', $excerpt );
	return $excerpt;
}

/**
 * Finds blocks by name from given blocks.
 *
 * @param array $blocks The haystack of Blocks to search in.
 * @param array $blocks_to_find The needle(s) of Blocks to search for.
 *
 * @return array $list The list of blocks found.
 */
function find_from_blocks( $blocks, $blocks_to_find ) {
	$list = array();

	foreach ( $blocks as $block ) {
		if ( in_array( $block['blockName'], $blocks_to_find ) ) {
			$list[] = $block;
		} elseif ( ! empty( $block['innerBlocks'] ) ) {
			// or call the function recursively, to find blocks in inner blocks
			$list = array_merge( $list, find_from_blocks( $block['innerBlocks'], $blocks_to_find ) );
		}
	}

	return $list;
}

/**
 * Determines if a link is external.
 *
 * @param string $link The link to check.
 *
 * @return boolean $external Whether the link is external.
 */
function is_link_external( $link ) {
	// Normalize link if it comes in as an array
	if ( is_array( $link ) ) {
		$link = isset( $link['url'] ) ? $link['url'] : $link;
	}

	// Empty links
	if ( empty( $link ) || ! is_string( $link ) ) {
		return false;
	}

	$link = trim( $link );

	// If it's a relative URL (starts with / or #), it's internal
	if ( strpos( $link, '/' ) === 0 || strpos( $link, '#' ) === 0 ) {
		return false;
	}

	// If it starts with something followed by a colon but NOT http, https, or //
	// treat it as internal (covers tel:, mailto:, sms:, dialpad:, etc.)
	if ( preg_match( '/^[a-z][a-z0-9+\-.]*:/i', $link ) && ! preg_match( '#^https?:#i', $link ) ) {
		return false;
	}

	// Handle protocol-relative URLs (//example.com)
	if ( strpos( $link, '//' ) === 0 ) {
		$link = 'https:' . $link;
	}

	// Compare hostnames
	$url_host = parse_url( $link, PHP_URL_HOST );
	$base_host = parse_url( site_url(), PHP_URL_HOST );

	if ( empty( $url_host ) || strcasecmp( $url_host, $base_host ) === 0 ) {
		return false;
	}

	return true;
}

/**
 * Calculates the coordinates for a circular area around a coordinate pair and
 * encodes them as an encoded polyline.
 *
 * @param float $lat The center latitude
 * @param float $lng The center longitude
 * @param float $rad The radius of the circle in meters
 * @param int   $points The number of points for drawing the radius
 *
 * @return string The encoded polyline string
 */
function gmap_circle_enc( $lat, $lng, $rad, $points = 8 ) {
	$earth_radius = 6371000;

	$lat = deg2rad( $lat );
	$lng = deg2rad( $lng );
	$d_bearing = 360 / $points;

	$coordinates = array();
	for ( $i = 0; $i < $points; $i++ ) {
		$bearing = deg2rad( $i * $d_bearing );

		$p_lat = asin( sin( $lat ) * cos( $rad / $earth_radius ) + cos( $lat ) * sin( $rad / $earth_radius ) * cos( $bearing ) );
		$p_lng = $lng + atan2( sin( $bearing ) * sin( $rad / $earth_radius ) * cos( $lat ), cos( $rad / $earth_radius ) - sin( $lat ) * sin( $p_lat ) );

		$p_lat = rad2deg( $p_lat );
		$p_lng = rad2deg( $p_lng );

		$coordinates[] = [
			'lat' => $p_lat,
			'lng' => $p_lng,
		];
	}

	if ( $points > 2 ) {
		$coordinates[] = $coordinates[0];
	}

	require_once 'classes/Polyline.php';
	$polyline = 'StrategyBlocks\Polyline';
	$encoded_string = $polyline::encode( $coordinates );

	return $encoded_string;
}

/**
 * Get the blocks field options for animations
 * If they exist they will return as an array, otherwise, an empty string.
 * return the animations for use in [data-animation].
 *
 * @param array $attributes    The $attributes object.
 *
 * @return string $animations A string of spaced delimited classes to add to blocks as [data-animation] attribute for animations to occur at observation.
 */
function get_animation_data( $attributes ) {
	$animation_options = [];

	if ( isset( $attributes['animationEntrance'] ) && 'none' !== $attributes['animationEntrance'] ) {
		$animation_options['animationEntrance'] = $attributes['animationEntrance'];
	} else {
		return null;
	}

	$animation_options['animationDuration'] = isset( $attributes['animationDuration'] ) ? $attributes['animationDuration'] : 'normal';

	if ( isset( $attributes['animationDelay'] ) ) {
		$animation_options['animationDelay'] = $attributes['animationDelay'];
	}

	// Filter out empty values and remove whitespace
	$animations_filtered = array_filter(
		$animation_options,
		function ( $value ) {
			// Return true for non-empty strings and not two single quotes
			return is_string( $value ) && trim( $value ) !== '' && "''" !== $value;
		}
	);

	// Return a space-delimited string if there are animations, otherwise return null
	return $animations_filtered ? implode( ' ', $animations_filtered ) : null;
}

/**
 * Function that inverts a color that is given to it.
 * Inverts primary <-> secondary, accent-1 <-> accent-2 and shade <-> tint.
 *
 * @param string $color The string to be parsed and inverted
 *
 * @return string The inverted color string
 */
function get_inverse_color( $color ) {
	// Parse the color first
	$inverse_color = $color;
	if ( false !== strpos( $color, 'primary' ) ) {
		$inverse_color = str_replace( 'primary', 'secondary', $inverse_color );
	} else if ( false !== strpos( $color, 'secondary' ) ) {
		$inverse_color = str_replace( 'secondary', 'primary', $inverse_color );
	} else if ( false !== strpos( $color, 'accent-1' ) ) {
		$inverse_color = str_replace( 'accent-1', 'accent-2', $inverse_color );
	} else if ( false !== strpos( $color, 'accent-2' ) ) {
		$inverse_color = str_replace( 'accent-2', 'accent-1', $inverse_color );
	}

	// Next we parse the shade
	$inverse_shade = $inverse_color;
	if ( false !== strpos( $inverse_color, 'shade' ) ) {
		$inverse_shade = str_replace( 'shade', 'tint', $inverse_shade );
	} else if ( false !== strpos( $inverse_color, 'tint' ) ) {
		$inverse_shade = str_replace( 'tint', 'shade', $inverse_shade );
	}

	return $inverse_shade;
}

/**
 * Checks the post and template parts for a certain block. This fixes a bug where WP doesn't run the has_block check on template parts.
 *
 * @param string $block_name             The block name to check for
 * @param array  $template_parts         An array of template parts returned by get_block_template
 *
 * @return boolean
 */
function truly_has_block( $block_name, $template_parts ) {
	if ( has_block( $block_name ) ) {
		return true;
	}
	foreach ( $template_parts as $part ) {
		if ( $part && has_block( $block_name, $part->content ) ) {
			return true;
		}
	}
	// Check for reusable blocks
	if ( has_block( 'core/block' ) ) {
		$content = get_post_field( 'post_content' );
		$blocks = parse_blocks( $content );
		return search_reusable_blocks_within_innerblocks( $blocks, $block_name );
	}
	return false;
}

/**
 * Search for selected block within inner blocks.
 *
 * @param array  $blocks Blocks to loop through.
 * @param string $block_name Full Block type to look for.
 * @return bool
 */
function search_reusable_blocks_within_innerblocks( $blocks, $block_name ) {
	foreach ( $blocks as $block ) {
		$has_block = false;
		// section /row /column /core
		if ( isset( $block['innerBlocks'] ) && ! empty( $block['innerBlocks'] ) ) {
			$has_block = search_reusable_blocks_within_innerblocks( $block['innerBlocks'], $block_name );
		} elseif ( 'core/block' === $block['blockName'] && ! empty( $block['attrs']['ref'] ) && has_block( $block_name, $block['attrs']['ref'] ) ) {
			$has_block = true;
		}
		if ( $has_block ) {
			return true;
		}
	}
	return false;
}

/**
 * Return blocks with their inner blocks flattened.
 *
 * @param array $blocks Array of blocks as returned by parse_blocks().
 * @return array All blocks.
 */
function flatten_blocks( $blocks ) {
	return array_reduce(
		$blocks,
		function ( $carry, $block ) {
			array_push( $carry, array_diff_key( $block, array_flip( array( 'innerBlocks' ) ) ) );
			if ( isset( $block['innerBlocks'] ) ) {
				$inner_blocks = flatten_blocks( $block['innerBlocks'] );
				return array_merge( $carry, $inner_blocks );
			}

			return $carry;
		},
		[]
	);
}

/**
 * Gets all images from blocks that are specified internally.
 *
 * @param array $blocks Blocks to loop through.
 * @return array
 *
 * TODO: This will need to be edited for native blocks.
 */
function get_block_images( $blocks ) {
	$images = [];
	$block_names = [ 'acf/section', 'acf/media' ];
	$all_blocks = flatten_blocks( $blocks );

	foreach ( $all_blocks as $block ) {
		$image = null;
		$image_id = null;
		$src = '';

		if ( in_array( $block['blockName'], $block_names ) ) {
			if ( ! empty( $block['attrs']['data'] ) ) {
				$data = $block['attrs']['data'];

				// Section Block
				if ( isset( $data['advanced_settings_background_image'] ) && ! empty( $data['advanced_settings_background_image'] ) ) {
					$image_id = $data['advanced_settings_background_image'];
					$image = wp_get_attachment_image_src( $image_id, 'full' );
				}

				// Media Block
				if ( isset( $data['image'] ) && ! empty( $data['image'] ) ) {
					$image_id = $data['image'];
					$image = wp_get_attachment_image_src( $image_id, $data['image_size'] );
				}

				if ( empty( $image ) || empty( $image_id ) ) {
					continue;
				}

				list( $src, $width, $height ) = $image;
				$image_meta = wp_get_attachment_metadata( $image_id );

				$image = [ 'src' => $src ];

				if ( is_array( $image_meta ) ) {
					$size_array = array( absint( $width ), absint( $height ) );
					$srcset = wp_calculate_image_srcset( $size_array, $src, $image_meta, $image_id );
					$sizes = wp_calculate_image_sizes( $size_array, $src, $image_meta, $image_id );

					if ( $srcset && ( $sizes || ! empty( $attr['sizes'] ) ) ) {
						$image['imagesrcset'] = $srcset;

						if ( empty( $attr['sizes'] ) ) {
							$image['imagesizes'] = $sizes;
						}
					}
				}
			}
		}

		if ( ! empty( $image ) ) {
			array_push( $images, $image );
		}
	}

	return $images;
}

/**
 * Outputs an accessible pagination list for archive or query loops, with SVG prev and next icons.
 *
 * Uses paginate_links with type set to list, wraps the prev and next icons with FontAwesomeSVG,
 * and sanitizes the final HTML through sanitize_and_output_svg. Prints nothing when total pages
 * is less than 2.
 *
 * Filters:
 * * lg_pagination_prev_svg: filter the Font Awesome icon id for the previous link.
 * * lg_pagination_next_svg: filter the Font Awesome icon id for the next link.
 *
 * @param array $args {
 *     Optional, arguments that control pagination output.
 *     @type int $total   Total number of pages, default 1.
 *     @type int $current Current page number, default 1.
 * }
 * @return void Echoes sanitized HTML directly.
 *
 * @since TBD
 * @see paginate_links()
 * @see FontAwesomeSVG
 * @see sanitize_and_output_svg()
 */
function pagination( $args = [] ) {
	$total   = isset( $args['total'] ) ? (int) $args['total'] : 1;
	$current = isset( $args['current'] ) ? (int) $args['current'] : 1;

	$font_awesome = new FontAwesomeSVG( STRATEGY_BLOCKS_DIST_PATH . 'font-awesome/svgs' );
	/**
	 * Filters the SVG markup used for the pagination previous icon.
	 *
	 * @since TBD
	 *
	 * @param string $icon_id   The default icon ID used to generate the SVG.
	 */
	$prev_icon_id = apply_filters( 'lg_pagination_prev_svg', 'fas fa-chevron-left' );
	/**
	 * Filters the SVG markup used for the pagination next icon.
	 *
	 * @since TBD
	 *
	 * @param string $icon_id   The default icon ID used to generate the SVG.
	 */
	$next_icon_id = apply_filters( 'lg_pagination_next_svg', 'fas fa-chevron-right' );
	$left_arrow = $font_awesome->get_svg( $prev_icon_id, [ 'class' => 'posts__pagination-icon' ] );
	$right_arrow = $font_awesome->get_svg( $next_icon_id, [ 'class' => 'posts__pagination-icon' ] );

	if ( $total > 1 ) {
		sanitize_and_output_svg(
			paginate_links(
				array(
					'base'      => str_replace( 999999999, '%#%', esc_url( get_pagenum_link( 999999999 ) ) ),
					'current'   => $current,
					'total'     => $total,
					// 'format' => '?paged=%#%',
					'prev_text' => '<span class="sr-only">Previous</span><span aria-hidden="true">' . sanitize_and_output_svg( $left_arrow, false ) . '</span>',
					'next_text' => '<span class="sr-only">Next</span><span aria-hidden="true">' . sanitize_and_output_svg( $right_arrow, false ) . '</span>',
					'type' => 'list',
				)
			)
		);
	}
}

/**
 * Pagination for paged posts, Page 1, Page 2, Page 3, with Next and Previous Links
 **/
function social_share() {
	$font_awesome = new FontAwesomeSVG( STRATEGY_BLOCKS_DIST_PATH . 'font-awesome/svgs' );
	$twitter = $font_awesome->get_svg( 'fab fa-x-twitter', [ 'class' => 'post__share-icon' ] );
	$facebook = $font_awesome->get_svg( 'fab fa-facebook-f', [ 'class' => 'post__share-icon' ] );
	$buffer = $font_awesome->get_svg( 'fab fa-buffer', [ 'class' => 'post__share-icon' ] );
	$linkedin = $font_awesome->get_svg( 'fab fa-linkedin-in', [ 'class' => 'post__share-icon' ] );
	$pinterest = $font_awesome->get_svg( 'fab fa-pinterest-p', [ 'class' => 'post__share-icon' ] );

	// Get current page URL
	$url = urlencode( get_permalink() );
	// Get current page title
	$title = htmlspecialchars( urlencode( html_entity_decode( get_the_title(), ENT_COMPAT, 'UTF-8' ) ), ENT_COMPAT, 'UTF-8' );
	// Get Post Thumbnail for pinterest
	$thumbnail = wp_get_attachment_image_src( get_post_thumbnail_id( get_the_ID() ), 'full' );
	if ( ! $thumbnail ) {
		$thumbnail = wp_get_attachment_image_src( get_theme_mod( 'custom_logo' ), 'full' );
	}
	// Construct the social sharing links
	$twitter_url = 'https://twitter.com/intent/tweet?text=' . $title . '&amp;url=' . $url . '&amp;via=LeadFuel';
	$facebook_url = 'https://www.facebook.com/sharer/sharer.php?u=' . $url;
	$buffer_url = 'https://bufferapp.com/add?url=' . $url . '&amp;text=' . $title;
	$linkedin_url = 'https://www.linkedin.com/shareArticle?mini=true&url=' . $url . '&amp;title=' . $title;
	$pinterest_url = 'https://pinterest.com/pin/create/button/?url=' . $url . '&amp;description=' . $title;
	if ( $thumbnail ) {
		$pinterest_url .= '&amp;media=' . $thumbnail[0];
	}

	?>
	<div class="post__extra post__share">
		<p class="post__share-text">Share the post:</p>
		<a class="post__share-link twitter" href="<?php echo esc_url( $twitter_url ); ?>" target="_blank"
			rel="noopener nofollow noreferrer" title="(opens in a new window)">
			<span class="sr-only">Twitter</span>
			<?php sanitize_and_output_svg( $twitter ); ?>
		</a>
		<a class="post__share-link facebook" href="<?php echo esc_url( $facebook_url ); ?>" target="_blank"
			rel="noopener nofollow noreferrer" title="(opens in a new window)">
			<span class="sr-only">Facebook</span>
			<?php sanitize_and_output_svg( $facebook ); ?>
		</a>
		<a class="post__share-link buffer" href="<?php echo esc_url( $buffer_url ); ?>" target="_blank"
			rel="noopener nofollow noreferrer" title="(opens in a new window)">
			<span class="sr-only">Buffer</span>
			<?php sanitize_and_output_svg( $buffer ); ?>
		</a>
		<a class="post__share-link linkedin" href="<?php echo esc_url( $linkedin_url ); ?>" target="_blank"
			rel="noopener nofollow noreferrer" title="(opens in a new window)">
			<span class="sr-only">LinkedIn</span>
			<?php sanitize_and_output_svg( $linkedin ); ?>
		</a>
		<a class="post__share-link pinterest" href="<?php echo esc_url( $pinterest_url ); ?>" data-pin-custom="true"
			target="_blank" rel="noopener nofollow noreferrer" title="(opens in a new window)">
			<span class="sr-only">Pin It</span>
			<?php sanitize_and_output_svg( $pinterest ); ?>
		</a>
	</div>
	<?php
}

/**
 * Render a single testimonial item.
 *
 * Outputs the testimonial markup based on display type and post data.
 *
 * @param int    $post_id      The testimonial post ID.
 * @param string $display_type Display type for the testimonial (e.g. 'card', 'quote', 'slider').
 * @param object $font_awesome Font Awesome SVG handler instance.
 *
 * @return string The rendered testimonial HTML.
 */
function render_testimonial_item( $post_id, $display_type, $font_awesome ) {
	$featured_image_id = get_post_thumbnail_id( $post_id );
	$rating            = get_field( 'rating', $post_id ) ? get_field( 'rating', $post_id ) : '';

	ob_start();
	?>
	<?php if ( 'slider' === $display_type ) : ?>
		<div class="swiper-slide">
	<?php else : ?>
		<div class="col col-xs-12 col-sm-6 col-md-4">
	<?php endif; ?>

	<div class="testimonial testimonial--<?php echo esc_attr( $display_type ); ?>">
		<figure class="testimonial__content">
			<?php if ( 'quote' === $display_type ) : ?>
				<?php
				$svg = $font_awesome->get_svg( 'fa-solid fa-quote-left', [ 'class' => 'testimonial__quote' ] );
				sanitize_and_output_svg( $svg );
				?>
			<?php endif; ?>

			<blockquote class="testimonial__text"><?php echo wp_kses_post( get_the_content( null, false, $post_id ) ); ?></blockquote>
			<figcaption class="testimonial__cite">
				<?php if ( $featured_image_id ) : ?>
					<?php echo wp_get_attachment_image( $featured_image_id, [ 75, 75 ], false, [ 'class' => 'testimonial__image' ] ); ?>
				<?php endif; ?>
				<span class="testimonial__name"><?php echo esc_html( get_the_title( $post_id ) ); ?></span>
				<time class="testimonial__date sr-only" datetime="<?php echo esc_attr( get_the_date( 'Y-m-d', $post_id ) ); ?>">
					<?php echo esc_html( get_the_date( '', $post_id ) ); ?>
				</time>
				<?php if ( $rating ) : ?>
					<div class="testimonial__rating">
						<span class="sr-only"><?php printf( esc_html__( 'Rating: %s', 'lead-gen' ), esc_html( $rating ) ); ?></span>
						<?php
						for ( $i = 0; $i < intval( $rating ); $i++ ) {
							$svg = $font_awesome->get_svg( 'fa-solid fa-star', [ 'class' => 'testimonial__rating-star' ] );
							sanitize_and_output_svg( $svg );
						}
						?>
					</div>
				<?php endif; ?>
			</figcaption>
		</figure>
	</div>

	</div>
	<?php
	return ob_get_clean();
}

/**
 * Gets the <svg> for the selected social media platform.
 *
 * @param string $social    The social media platform slug to get the svg for.
 *
 * @return string           The <svg> markup as a string. You must echo it yourself.
 */
function get_social_icon( $social ) {

	switch ( $social ) {
		case 'facebook':
			$path = '<path d="M504 256C504 119 393 8 256 8S8 119 8 256c0 123.78 90.69 226.38 209.25 245V327.69h-63V256h63v-54.64c0-62.15 37-96.48 93.67-96.48 27.14 0 55.52 4.84 55.52 4.84v61h-31.28c-30.8 0-40.41 19.12-40.41 38.73V256h68.78l-11 71.69h-57.78V501C413.31 482.38 504 379.78 504 256z"/>';
			$view_box = '0 0 512 512';
			break;
		case 'twitter':
			$path = '<path d="M389.2 48h70.6L305.6 224.2 487 464H345L233.7 318.6 106.5 464H35.8L200.7 275.5 26.8 48H172.4L272.9 180.9 389.2 48zM364.4 421.8h39.1L151.1 88h-42L364.4 421.8z"/>';
			$view_box = '0 0 512 512';
			break;
		case 'instagram':
			$path = '<path d="M224.1 141c-63.6 0-114.9 51.3-114.9 114.9s51.3 114.9 114.9 114.9S339 319.5 339 255.9 287.7 141 224.1 141zm0 189.6c-41.1 0-74.7-33.5-74.7-74.7s33.5-74.7 74.7-74.7 74.7 33.5 74.7 74.7-33.6 74.7-74.7 74.7zm146.4-194.3c0 14.9-12 26.8-26.8 26.8-14.9 0-26.8-12-26.8-26.8s12-26.8 26.8-26.8 26.8 12 26.8 26.8zm76.1 27.2c-1.7-35.9-9.9-67.7-36.2-93.9-26.2-26.2-58-34.4-93.9-36.2-37-2.1-147.9-2.1-184.9 0-35.8 1.7-67.6 9.9-93.9 36.1s-34.4 58-36.2 93.9c-2.1 37-2.1 147.9 0 184.9 1.7 35.9 9.9 67.7 36.2 93.9s58 34.4 93.9 36.2c37 2.1 147.9 2.1 184.9 0 35.9-1.7 67.7-9.9 93.9-36.2 26.2-26.2 34.4-58 36.2-93.9 2.1-37 2.1-147.8 0-184.8zM398.8 388c-7.8 19.6-22.9 34.7-42.6 42.6-29.5 11.7-99.5 9-132.1 9s-102.7 2.6-132.1-9c-19.6-7.8-34.7-22.9-42.6-42.6-11.7-29.5-9-99.5-9-132.1s-2.6-102.7 9-132.1c7.8-19.6 22.9-34.7 42.6-42.6 29.5-11.7 99.5-9 132.1-9s102.7-2.6 132.1 9c19.6 7.8 34.7 22.9 42.6 42.6 11.7 29.5 9 99.5 9 132.1s2.7 102.7-9 132.1z"/>';
			$view_box = '0 0 448 512';
			break;
		case 'youtube':
			$path = '<path d="M549.655 124.083c-6.281-23.65-24.787-42.276-48.284-48.597C458.781 64 288 64 288 64S117.22 64 74.629 75.486c-23.497 6.322-42.003 24.947-48.284 48.597-11.412 42.867-11.412 132.305-11.412 132.305s0 89.438 11.412 132.305c6.281 23.65 24.787 41.5 48.284 47.821C117.22 448 288 448 288 448s170.78 0 213.371-11.486c23.497-6.321 42.003-24.171 48.284-47.821 11.412-42.867 11.412-132.305 11.412-132.305s0-89.438-11.412-132.305zm-317.51 213.508V175.185l142.739 81.205-142.739 81.201z"/>';
			$view_box = '0 0 576 512';
			break;
		case 'google':
			$path = '<path d="M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z"/>';
			$view_box = '0 0 488 512';
			break;
		case 'linkedin':
			$path = '<path d="M100.28 448H7.4V148.9h92.88zM53.79 108.1C24.09 108.1 0 83.5 0 53.8a53.79 53.79 0 0 1 107.58 0c0 29.7-24.1 54.3-53.79 54.3zM447.9 448h-92.68V302.4c0-34.7-.7-79.2-48.29-79.2-48.29 0-55.69 37.7-55.69 76.7V448h-92.78V148.9h89.08v40.8h1.3c12.4-23.5 42.69-48.3 87.88-48.3 94 0 111.28 61.9 111.28 142.3V448z"/>';
			$view_box = '0 0 448 512';
			break;
		case 'pinterest':
			$path = '<path d="M496 256c0 137-111 248-248 248-25.6 0-50.2-3.9-73.4-11.1 10.1-16.5 25.2-43.5 30.8-65 3-11.6 15.4-59 15.4-59 8.1 15.4 31.7 28.5 56.8 28.5 74.8 0 128.7-68.8 128.7-154.3 0-81.9-66.9-143.2-152.9-143.2-107 0-163.9 71.8-163.9 150.1 0 36.4 19.4 81.7 50.3 96.1 4.7 2.2 7.2 1.2 8.3-3.3.8-3.4 5-20.3 6.9-28.1.6-2.5.3-4.7-1.7-7.1-10.1-12.5-18.3-35.3-18.3-56.6 0-54.7 41.4-107.6 112-107.6 60.9 0 103.6 41.5 103.6 100.9 0 67.1-33.9 113.6-78 113.6-24.3 0-42.6-20.1-36.7-44.8 7-29.5 20.5-61.3 20.5-82.6 0-19-10.2-34.9-31.4-34.9-24.9 0-44.9 25.7-44.9 60.2 0 22 7.4 36.8 7.4 36.8s-24.5 103.8-29 123.2c-5 21.4-3 51.6-.9 71.2C65.4 450.9 0 361.1 0 256 0 119 111 8 248 8s248 111 248 248z"/>';
			$view_box = '0 0 496 512';
			break;
		case 'discord':
			$path = '<path d="M524.531,69.836a1.5,1.5,0,0,0-.764-.7A485.065,485.065,0,0,0,404.081,32.03a1.816,1.816,0,0,0-1.923.91,337.461,337.461,0,0,0-14.9,30.6,447.848,447.848,0,0,0-134.426,0,309.541,309.541,0,0,0-15.135-30.6,1.89,1.89,0,0,0-1.924-.91A483.689,483.689,0,0,0,116.085,69.137a1.712,1.712,0,0,0-.788.676C39.068,183.651,18.186,294.69,28.43,404.354a2.016,2.016,0,0,0,.765,1.375A487.666,487.666,0,0,0,176.02,479.918a1.9,1.9,0,0,0,2.063-.676A348.2,348.2,0,0,0,208.12,430.4a1.86,1.86,0,0,0-1.019-2.588,321.173,321.173,0,0,1-45.868-21.853,1.885,1.885,0,0,1-.185-3.126c3.082-2.309,6.166-4.711,9.109-7.137a1.819,1.819,0,0,1,1.9-.256c96.229,43.917,200.41,43.917,295.5,0a1.812,1.812,0,0,1,1.924.233c2.944,2.426,6.027,4.851,9.132,7.16a1.884,1.884,0,0,1-.162,3.126,301.407,301.407,0,0,1-45.89,21.83,1.875,1.875,0,0,0-1,2.611,391.055,391.055,0,0,0,30.014,48.815,1.864,1.864,0,0,0,2.063.7A486.048,486.048,0,0,0,610.7,405.729a1.882,1.882,0,0,0,.765-1.352C623.729,277.594,590.933,167.465,524.531,69.836ZM222.491,337.58c-28.972,0-52.844-26.587-52.844-59.239S193.056,219.1,222.491,219.1c29.665,0,53.306,26.82,52.843,59.239C275.334,310.993,251.924,337.58,222.491,337.58Zm195.38,0c-28.971,0-52.843-26.587-52.843-59.239S388.437,219.1,417.871,219.1c29.667,0,53.307,26.82,52.844,59.239C470.715,310.993,447.538,337.58,417.871,337.58Z"/>';
			$view_box = '0 0 640 512';
			break;
		case 'github':
			$path = '<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/>';
			$view_box = '0 0 496 512';
			break;
		case 'snapchat':
			$path = '<path d="M496.926,366.6c-3.373-9.176-9.8-14.086-17.112-18.153-1.376-.806-2.641-1.451-3.72-1.947-2.182-1.128-4.414-2.22-6.634-3.373-22.8-12.09-40.609-27.341-52.959-45.42a102.889,102.889,0,0,1-9.089-16.12c-1.054-3.013-1-4.724-.248-6.287a10.221,10.221,0,0,1,2.914-3.038c3.918-2.591,7.96-5.22,10.7-6.993,4.885-3.162,8.754-5.667,11.246-7.44,9.362-6.547,15.909-13.5,20-21.278a42.371,42.371,0,0,0,2.1-35.191c-6.2-16.318-21.613-26.449-40.287-26.449a55.543,55.543,0,0,0-11.718,1.24c-1.029.224-2.059.459-3.063.72.174-11.16-.074-22.94-1.066-34.534-3.522-40.758-17.794-62.123-32.674-79.16A130.167,130.167,0,0,0,332.1,36.443C309.515,23.547,283.91,17,256,17S202.6,23.547,180,36.443a129.735,129.735,0,0,0-33.281,26.783c-14.88,17.038-29.152,38.44-32.673,79.161-.992,11.594-1.24,23.435-1.079,34.533-1-.26-2.021-.5-3.051-.719a55.461,55.461,0,0,0-11.717-1.24c-18.687,0-34.125,10.131-40.3,26.449a42.423,42.423,0,0,0,2.046,35.228c4.105,7.774,10.652,14.731,20.014,21.278,2.48,1.736,6.361,4.24,11.246,7.44,2.641,1.711,6.5,4.216,10.28,6.72a11.054,11.054,0,0,1,3.3,3.311c.794,1.624.818,3.373-.36,6.6a102.02,102.02,0,0,1-8.94,15.785c-12.077,17.669-29.363,32.648-51.434,44.639C32.355,348.608,20.2,352.75,15.069,366.7c-3.868,10.528-1.339,22.506,8.494,32.6a49.137,49.137,0,0,0,12.4,9.387,134.337,134.337,0,0,0,30.342,12.139,20.024,20.024,0,0,1,6.126,2.741c3.583,3.137,3.075,7.861,7.849,14.78a34.468,34.468,0,0,0,8.977,9.127c10.019,6.919,21.278,7.353,33.207,7.811,10.776.41,22.989.881,36.939,5.481,5.778,1.91,11.78,5.605,18.736,9.92C194.842,480.951,217.707,495,255.973,495s61.292-14.123,78.118-24.428c6.907-4.24,12.872-7.9,18.489-9.758,13.949-4.613,26.163-5.072,36.939-5.481,11.928-.459,23.187-.893,33.206-7.812a34.584,34.584,0,0,0,10.218-11.16c3.434-5.84,3.348-9.919,6.572-12.771a18.971,18.971,0,0,1,5.753-2.629A134.893,134.893,0,0,0,476.02,408.71a48.344,48.344,0,0,0,13.019-10.193l.124-.149C498.389,388.5,500.708,376.867,496.926,366.6Zm-34.013,18.277c-20.745,11.458-34.533,10.23-45.259,17.137-9.114,5.865-3.72,18.513-10.342,23.076-8.134,5.617-32.177-.4-63.239,9.858-25.618,8.469-41.961,32.822-88.038,32.822s-62.036-24.3-88.076-32.884c-31-10.255-55.092-4.241-63.239-9.858-6.609-4.563-1.24-17.211-10.341-23.076-10.739-6.907-24.527-5.679-45.26-17.075-13.206-7.291-5.716-11.8-1.314-13.937,75.143-36.381,87.133-92.552,87.666-96.719.645-5.046,1.364-9.014-4.191-14.148-5.369-4.96-29.189-19.7-35.8-24.316-10.937-7.638-15.748-15.264-12.2-24.638,2.48-6.485,8.531-8.928,14.879-8.928a27.643,27.643,0,0,1,5.965.67c12,2.6,23.659,8.617,30.392,10.242a10.749,10.749,0,0,0,2.48.335c3.6,0,4.86-1.811,4.612-5.927-.768-13.132-2.628-38.725-.558-62.644,2.84-32.909,13.442-49.215,26.04-63.636,6.051-6.932,34.484-36.976,88.857-36.976s82.88,29.92,88.931,36.827c12.611,14.421,23.225,30.727,26.04,63.636,2.071,23.919.285,49.525-.558,62.644-.285,4.327,1.017,5.927,4.613,5.927a10.648,10.648,0,0,0,2.48-.335c6.745-1.624,18.4-7.638,30.4-10.242a27.641,27.641,0,0,1,5.964-.67c6.386,0,12.4,2.48,14.88,8.928,3.546,9.374-1.24,17-12.189,24.639-6.609,4.612-30.429,19.343-35.8,24.315-5.568,5.134-4.836,9.1-4.191,14.149.533,4.228,12.511,60.4,87.666,96.718C468.629,373.011,476.119,377.524,462.913,384.877Z"/>';
			$view_box = '0 0 512 512';
			break;
		case 'reddit':
			$path = '<path d="M201.5 305.5c-13.8 0-24.9-11.1-24.9-24.6 0-13.8 11.1-24.9 24.9-24.9 13.6 0 24.6 11.1 24.6 24.9 0 13.6-11.1 24.6-24.6 24.6zM504 256c0 137-111 248-248 248S8 393 8 256 119 8 256 8s248 111 248 248zm-132.3-41.2c-9.4 0-17.7 3.9-23.8 10-22.4-15.5-52.6-25.5-86.1-26.6l17.4-78.3 55.4 12.5c0 13.6 11.1 24.6 24.6 24.6 13.8 0 24.9-11.3 24.9-24.9s-11.1-24.9-24.9-24.9c-9.7 0-18 5.8-22.1 13.8l-61.2-13.6c-3-.8-6.1 1.4-6.9 4.4l-19.1 86.4c-33.2 1.4-63.1 11.3-85.5 26.8-6.1-6.4-14.7-10.2-24.1-10.2-34.9 0-46.3 46.9-14.4 62.8-1.1 5-1.7 10.2-1.7 15.5 0 52.6 59.2 95.2 132 95.2 73.1 0 132.3-42.6 132.3-95.2 0-5.3-.6-10.8-1.9-15.8 31.3-16 19.8-62.5-14.9-62.5zM302.8 331c-18.2 18.2-76.1 17.9-93.6 0-2.2-2.2-6.1-2.2-8.3 0-2.5 2.5-2.5 6.4 0 8.6 22.8 22.8 87.3 22.8 110.2 0 2.5-2.2 2.5-6.1 0-8.6-2.2-2.2-6.1-2.2-8.3 0zm7.7-75c-13.6 0-24.6 11.1-24.6 24.9 0 13.6 11.1 24.6 24.6 24.6 13.8 0 24.9-11.1 24.9-24.6 0-13.8-11-24.9-24.9-24.9z"/>';
			$view_box = '0 0 512 512';
			break;
		case 'twitch':
			$path = '<path d="M391.17,103.47H352.54v109.7h38.63ZM285,103H246.37V212.75H285ZM120.83,0,24.31,91.42V420.58H140.14V512l96.53-91.42h77.25L487.69,256V0ZM449.07,237.75l-77.22,73.12H294.61l-67.6,64v-64H140.14V36.58H449.07Z"/>';
			$view_box = '0 0 512 512';
			break;
		case 'tumblr':
			$path = '<path d="M309.8 480.3c-13.6 14.5-50 31.7-97.4 31.7-120.8 0-147-88.8-147-140.6v-144H17.9c-5.5 0-10-4.5-10-10v-68c0-7.2 4.5-13.6 11.3-16 62-21.8 81.5-76 84.3-117.1.8-11 6.5-16.3 16.1-16.3h70.9c5.5 0 10 4.5 10 10v115.2h83c5.5 0 10 4.4 10 9.9v81.7c0 5.5-4.5 10-10 10h-83.4V360c0 34.2 23.7 53.6 68 35.8 4.8-1.9 9-3.2 12.7-2.2 3.5.9 5.8 3.4 7.4 7.9l22 64.3c1.8 5 3.3 10.6-.4 14.5z"/>';
			$view_box = '0 0 320 512';
			break;
		case 'etsy':
			$path = '<path d="M384 348c-1.75 10.75-13.75 110-15.5 132-117.879-4.299-219.895-4.743-368.5 0v-25.5c45.457-8.948 60.627-8.019 61-35.25 1.793-72.322 3.524-244.143 0-322-1.029-28.46-12.13-26.765-61-36v-25.5c73.886 2.358 255.933 8.551 362.999-3.75-3.5 38.25-7.75 126.5-7.75 126.5H332C320.947 115.665 313.241 68 277.25 68h-137c-10.25 0-10.75 3.5-10.75 9.75V241.5c58 .5 88.5-2.5 88.5-2.5 29.77-.951 27.56-8.502 40.75-65.251h25.75c-4.407 101.351-3.91 61.829-1.75 160.25H257c-9.155-40.086-9.065-61.045-39.501-61.5 0 0-21.5-2-88-2v139c0 26 14.25 38.25 44.25 38.25H263c63.636 0 66.564-24.996 98.751-99.75H384z"/>';
			$view_box = '0 0 384 512';
			break;
		case 'tiktok':
			$path = '<path d="M448.5 209.9c-44 .1-87-13.6-122.8-39.2l0 178.7c0 33.1-10.1 65.4-29 92.6s-45.6 48-76.6 59.6-64.8 13.5-96.9 5.3-60.9-25.9-82.7-50.8-35.3-56-39-88.9 2.9-66.1 18.6-95.2 40-52.7 69.6-67.7 62.9-20.5 95.7-16l0 89.9c-15-4.7-31.1-4.6-46 .4s-27.9 14.6-37 27.3-14 28.1-13.9 43.9 5.2 31 14.5 43.7 22.4 22.1 37.4 26.9 31.1 4.8 46-.1 28-14.4 37.2-27.1 14.2-28.1 14.2-43.8l0-349.4 88 0c-.1 7.4 .6 14.9 1.9 22.2 3.1 16.3 9.4 31.9 18.7 45.7s21.3 25.6 35.2 34.6c19.9 13.1 43.2 20.1 67 20.1l0 87.4z"/>';
			$view_box = '0 0 448 512';
			break;
		default:
			$path = '';
	}

	$svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="' . esc_attr( $view_box ) . '" width="1em" height="1em" aria-hidden="true">';
	$svg .= $path;
	$svg .= '</svg>';

	return $svg;
}

/**
 * Render slider navigation controls.
 *
 * Outputs the previous/next arrow buttons and pagination for the slider.
 * Allows customization of the SVG icons through filters.
 *
 * @param object $font_awesome Font Awesome SVG handler instance used to generate arrow icons.
 *
 * @return void Echoes the slider navigation markup.
 */
function render_slider_controls( $font_awesome ) {
	/**
	 * Filters the SVG markup used for the slider previous icon.
	 *
	 * @since TBD
	 *
	 * @param string $icon_id   The default icon ID used to generate the SVG.
	 */
	$prev_icon_id = apply_filters( 'lg_slider_prev_svg', 'fas fa-chevron-left' );
	/**
	 * Filters the SVG markup used for the slider next icon.
	 *
	 * @since TBD
	 *
	 * @param string $icon_id   The default icon ID used to generate the SVG.
	 */
	$next_icon_id = apply_filters( 'lg_slider_next_svg', 'fas fa-chevron-right' );
	$left_arrow   = $font_awesome->get_svg( $prev_icon_id, [ 'class' => 'swiper__arrow-icon' ] );
	$right_arrow  = $font_awesome->get_svg( $next_icon_id, [ 'class' => 'swiper__arrow-icon' ] );
	?>
	<div class="swiper__slider-navigation">
		<button class="swiper__slider-prev btn-link">
			<span class="sr-only"><?php esc_html_e( 'Previous Slide', 'lead-gen' ); ?></span>
			<?php sanitize_and_output_svg( $left_arrow ); ?>
		</button>
		<div class="swiper-pagination swiper__slider-pagination"></div>
		<button class="swiper__slider-next btn-link">
			<span class="sr-only"><?php esc_html_e( 'Next Slide', 'lead-gen' ); ?></span>
			<?php sanitize_and_output_svg( $right_arrow ); ?>
		</button>
	</div>
	<?php
}

/**
 * Builds the Google Static Maps API URL with markers and optional areas.
 *
 * @param array $params {
 *     Parameters for generating the static map.
 *
 *     @type int|null   $width      The map image width.
 *     @type int|null   $height     The map image height.
 *     @type int|null   $zoom       Optional zoom override.
 *     @type array      $hex_colors Array with 'primary' and 'secondary' hex colors converted to 0x format.
 *     @type array      $markers    Array of markers, each with title, location, and optional area settings.
 *     @type array|null $image      Optional fallback image array (from WP media).
 *     @type int        $font_size  Base font size used for placeholder fallback.
 *     @type string     $pin_color  Default marker pin color.
 * }
 *
 * @return string The generated static map URL or fallback image URL.
 */
function get_google_map_static_url( $params ) {
	$width      = $params['width'] ?? null;
	$height     = $params['height'] ?? null;
	$zoom       = $params['zoom'] ?? 16;
	$hex_colors = $params['hex_colors'] ?? [];
	$markers    = $params['markers'] ?? [];
	$image_id   = $params['image'] ?? 0;
	$font_size  = $params['font_size'] ?? 17;
	$pin_color  = $params['pin_color'] ?? 'red';
	$static_url = '';

	if ( ! empty( $markers ) && null != $width && null != $height ) {
		$static_url = 'https://maps.googleapis.com/maps/api/staticmap?maptype=roadmap&size=' . $width . 'x' . $height . '&zoom=' . $zoom;

		foreach ( $markers as $marker ) {
			$title = $marker['title'] ?? '';
			$lat = $marker['lat'] ?? '';
			$lng = $marker['lng'] ?? '';

			if ( $marker && $title && $lat && $lng ) {
				$area = filter_var( $marker['area'], FILTER_VALIDATE_BOOLEAN );
				$show = filter_var( $marker['show'], FILTER_VALIDATE_BOOLEAN );
				$show_marker = $area ? $show : true;
				$pin = $pin_color;
				if ( ! $area ) {
					if ( 'lightblue' === $pin ) {
						$pin = 'ltblue';
					}
					$pin .= '-dot';
				}
				if ( ! $area || $show_marker ) {
					$static_url .= '&markers=icon:https://maps.google.com/mapfiles/ms/icons/' . $pin . '.png|label:' . $title . '|' . $lat . ',' . $lng;
				}
				if ( $area ) {
					$radius = (int) $marker['radius'] ?? 0;
					$enc = gmap_circle_enc( $lat, $lng, $radius * 1609.34, 90 );
					$static_url .= '&path=fillcolor:' . $hex_colors['secondary'] . '|color:' . $hex_colors['primary'] . '|enc:' . urlencode( $enc );
				}
			}
		}
		$api_key = defined( 'GOOGLE_API_TOKEN' ) ? GOOGLE_API_TOKEN : DEVELOPER_GOOGLE_API_TOKEN;
		$static_url .= '&key=' . $api_key;
	} else if ( $image_id ) {
		$static_url = wp_get_attachment_url( $image_id );
	} else {
		$static_url = 'https://placehold.co/1920x' . $font_size * 26;
	}

	return $static_url;
}
