<?php
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * The plugin's icon library.
 *
 * Icons are a FOLDER OF SVGs the plugin owns (assets/icons/). The AI uses them with Lucide's own
 * syntax — <i data-lucide="name"></i> — and at render time we swap in the vetted inline SVG. Safe
 * (our files only, allow-listed names), light (only the icons actually used are read and printed)
 * and dependency-free (inline SVG).
 *
 * To customise:
 *  - drop in or replace assets/icons/<name>.svg (the full Lucide set works as a drop-in)
 *  - or use the filter: add_filter('aipb_icons', fn($m) => $m + ['my-logo' => '<svg>…</svg>'])
 *
 * The list of available names is sent to the service with the site payload; the sentence that
 * teaches the model to use them is composed there ({@see Aura\Prompt\Composer}), not here.
 */
class AIPB_Icons {

	/** Prepared SVGs, by name. One read per icon per request. */
	protected static $cache = array();

	/** The available names, resolved once. Null means "not looked up yet". */
	protected static $names = null;

	/** The icons folder. */
	public static function dir() {
		return trailingslashit( apply_filters( 'aipb_icons_dir', AIPB_PATH . 'assets/icons' ) );
	}

	/** Available names: the .svg basenames, plus anything added through the filter. */
	public static function names() {
		if ( null !== self::$names ) {
			return self::$names;
		}

		$names = array();
		foreach ( (array) glob( self::dir() . '*.svg' ) as $file ) {
			$names[] = basename( $file, '.svg' );
		}

		$custom = apply_filters( 'aipb_icons', array() );
		if ( is_array( $custom ) ) {
			$names = array_merge( $names, array_keys( $custom ) );
		}

		sort( $names );
		self::$names = array_values( array_unique( $names ) );

		return self::$names;
	}

	/** The safe, normalised SVG for a name; '' when there is none. */
	public static function svg( $name ) {
		$name = preg_replace( '/[^a-z0-9\-]/', '', strtolower( (string) $name ) );
		if ( '' === $name ) {
			return '';
		}
		if ( isset( self::$cache[ $name ] ) ) {
			return self::$cache[ $name ];
		}

		$custom = apply_filters( 'aipb_icons', array() );

		if ( is_array( $custom ) && isset( $custom[ $name ] ) ) {
			$svg = (string) $custom[ $name ];
		} else {
			// $name is already reduced to [a-z0-9-]: no path traversal is possible.
			$file = self::dir() . $name . '.svg';
			$svg  = is_file( $file ) ? (string) file_get_contents( $file ) : ''; // phpcs:ignore WordPress.WP.AlternativeFunctions
		}

		self::$cache[ $name ] = self::prepare( $svg );

		return self::$cache[ $name ];
	}

	/** Replaces <i data-lucide="name"></i> (or <span>) with the inline SVG. */
	public static function resolve( $html ) {
		if ( false === strpos( $html, 'data-lucide' ) ) {
			return $html;
		}

		return preg_replace_callback(
			'#<(i|span)\b([^>]*)\bdata-lucide\s*=\s*"([a-z0-9\-]+)"([^>]*)>\s*</\1\s*>#i',
			function ( $m ) {
				$svg = self::svg( $m[3] );
				if ( '' === $svg ) {
					return ''; // unknown name -> nothing, rather than broken markup
				}

				// Carry the element's class/style onto the SVG, for size and margins.
				$attributes = $m[2] . ' ' . $m[4];
				$inject     = '';

				if ( preg_match( '/\bclass\s*=\s*"([^"]*)"/i', $attributes, $class ) ) {
					$inject .= ' class="' . esc_attr( $class[1] ) . '"';
				}
				if ( preg_match( '/\bstyle\s*=\s*"([^"]*)"/i', $attributes, $style ) ) {
					$inject .= ' style="' . esc_attr( $style[1] ) . '"';
				}

				return '' !== $inject ? preg_replace( '/<svg\b/i', '<svg' . $inject, $svg, 1 ) : $svg;
			},
			$html
		);
	}

	/** Sanitises and normalises an SVG: no scripts or handlers, currentColor, 1em by default. */
	protected static function prepare( $svg ) {
		if ( '' === $svg || false === stripos( $svg, '<svg' ) ) {
			return '';
		}

		// Defence in depth, for the day somebody drops untrusted SVGs into the folder.
		$svg = preg_replace( '#<script\b[^>]*>.*?</script>#is', '', $svg );
		$svg = preg_replace( '#</?(script|foreignObject|iframe)\b[^>]*>#i', '', $svg );
		$svg = preg_replace( '/\son\w+\s*=\s*"[^"]*"/i', '', $svg );
		$svg = preg_replace( "/\son\w+\s*=\s*'[^']*'/i", '', $svg );
		$svg = preg_replace( '/(href|xlink:href)\s*=\s*"\s*javascript:[^"]*"/i', '', $svg );

		// A default size that scales with the font, when the file does not set one.
		// Note: \swidth and not \bwidth, so it does not match stroke-width.
		if ( ! preg_match( '/<svg[^>]*\swidth\s*=/i', $svg ) ) {
			$svg = preg_replace( '/<svg\b/i', '<svg width="1em" height="1em"', $svg, 1 );
		}

		return trim( $svg );
	}
}
