<?php
/**
 * Local asset data URI service.
 *
 * @package EntryFoundry
 */

namespace EntryFoundry\Core\Services\Assets;

use RuntimeException;

// Exception messages are internal diagnostics, not rendered HTML output.
// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped

/**
 * Converts explicitly local assets into portable data URIs.
 */
final class AssetDataUri {
	/**
	 * Plugin root used to resolve asset paths.
	 *
	 * @var string
	 */
	private $base_path;

	/**
	 * Initialize the asset service.
	 *
	 * @param string $base_path Plugin root.
	 */
	public function __construct( $base_path ) {
		$this->base_path = rtrim( $base_path, '/\\' );
	}

	/**
	 * Convert a plugin-relative asset into a data URI.
	 *
	 * @param string $relative_path Relative asset path.
	 * @return string
	 * @throws RuntimeException When the asset is invalid.
	 */
	public function from_relative_path( $relative_path ) {
		$relative_path = str_replace( '\\', '/', $relative_path );
		if ( false !== strpos( $relative_path, '..' ) || preg_match( '#^[a-z]+:#i', $relative_path ) ) {
			throw new RuntimeException( 'Asset source must be a plugin-relative local path.' );
		}

		$path = $this->base_path . DIRECTORY_SEPARATOR . str_replace( '/', DIRECTORY_SEPARATOR, ltrim( $relative_path, '/' ) );

		return $this->from_path( $path, $relative_path );
	}

	/**
	 * Convert a WordPress image attachment into a portable data URI.
	 *
	 * @param int $attachment_id Attachment post ID.
	 * @return string Empty when WordPress or the attachment is unavailable.
	 */
	public function from_attachment_id( int $attachment_id ): string {
		if ( $attachment_id < 1 || ! function_exists( 'get_attached_file' ) ) {
			return '';
		}

		$path = get_attached_file( $attachment_id );
		if ( ! is_string( $path ) || '' === $path ) {
			return '';
		}

		return $this->from_path( $path, 'attachment #' . $attachment_id );
	}

	/**
	 * Encode a trusted local image/font file.
	 *
	 * @param string $path  Absolute file path.
	 * @param string $label Diagnostic label.
	 * @return string
	 * @throws RuntimeException When the asset is missing or unsupported.
	 */
	private function from_path( string $path, string $label ): string {
		if ( ! is_file( $path ) || ! is_readable( $path ) ) {
			throw new RuntimeException( 'Asset is missing or unreadable: ' . $label );
		}

		$extension = strtolower( pathinfo( $path, PATHINFO_EXTENSION ) );
		$mime_map  = array(
			'gif'  => 'image/gif',
			'jpeg' => 'image/jpeg',
			'jpg'  => 'image/jpeg',
			'png'  => 'image/png',
			'svg'  => 'image/svg+xml',
			'ttf'  => 'font/ttf',
			'webp' => 'image/webp',
		);
		if ( ! isset( $mime_map[ $extension ] ) ) {
			throw new RuntimeException( 'Unsupported local asset type: ' . $extension );
		}

		// Verified plugin-local asset; WordPress may not be loaded.
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		$data = file_get_contents( $path );
		if ( false === $data ) {
			throw new RuntimeException( 'Unable to read asset: ' . $label );
		}

		// Base64 is the required transport encoding for a benign data URI.
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
		return 'data:' . $mime_map[ $extension ] . ';base64,' . base64_encode( $data );
	}
}
