<?php
/**
 * Lazy-load and LQIP blur-placeholder feature.
 *
 * Generates a tiny base64 JPEG thumbnail for each optimized image and stores
 * it as post meta (_naveencodes_lqip). On the front end, img tags that carry
 * the data-nc-lqip attribute show the blurred placeholder instantly while the
 * full image loads, then fade to the sharp version.
 *
 * @package NaveenCodesImageOptimizer
 */

namespace NaveenCodesImageOptimizer;

defined( 'ABSPATH' ) || exit;

/**
 * Handles LQIP generation and front-end lazy-load enhancements.
 */
class Lazyload {

	/**
	 * Post meta key for the stored LQIP data URI.
	 */
	const META_KEY = '_naveencodes_lqip';

	/**
	 * Width in pixels of the generated thumbnail.
	 */
	const LQIP_WIDTH = 20;

	/**
	 * JPEG quality for the LQIP thumbnail (very low = tiny file).
	 */
	const LQIP_QUALITY = 10;

	/**
	 * Settings service.
	 *
	 * @var Settings
	 */
	protected $settings;

	/**
	 * Constructor — registers hooks when the relevant settings are on.
	 *
	 * @param Settings $settings Settings service.
	 */
	public function __construct( Settings $settings ) {
		$this->settings = $settings;

		// Generate LQIP whenever an image is successfully optimized.
		add_action( 'naveencodes_image_optimized', array( $this, 'generate_lqip' ) );

		// Front-end only: inject data-nc-lqip and enqueue assets.
		if ( ! is_admin() ) {
			if ( $this->settings->get( 'lqip_enabled' ) ) {
				add_filter( 'wp_get_attachment_image_attributes', array( $this, 'add_lqip_attrs' ), 10, 2 );
				add_filter( 'the_content', array( $this, 'add_lqip_to_content' ), 20 );
			}

			add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend' ) );
		}
	}

	/* ── LQIP Generation ─────────────────────────────────────────────────── */

	/**
	 * Generate a tiny JPEG placeholder, base64-encode it, and store as meta.
	 *
	 * Called automatically via the naveencodes_image_optimized action after
	 * each successful optimization. Safe to call multiple times — skips if
	 * meta already exists and regeneration is not forced.
	 *
	 * @param int  $attachment_id Attachment post ID.
	 * @param bool $force         Regenerate even if meta already exists.
	 * @return string Data URI string, or empty string on failure.
	 */
	public function generate_lqip( $attachment_id, $force = false ) {
		$attachment_id = absint( $attachment_id );
		if ( ! $attachment_id ) {
			return '';
		}

		if ( ! $force && get_post_meta( $attachment_id, self::META_KEY, true ) ) {
			return (string) get_post_meta( $attachment_id, self::META_KEY, true );
		}

		$mime = get_post_mime_type( $attachment_id );
		if ( ! in_array( $mime, array( 'image/jpeg', 'image/png', 'image/webp', 'image/gif' ), true ) ) {
			return '';
		}

		$file = get_attached_file( $attachment_id );
		if ( ! $file || ! file_exists( $file ) ) {
			return '';
		}

		$editor = wp_get_image_editor( $file );
		if ( is_wp_error( $editor ) ) {
			return '';
		}

		$editor->resize( self::LQIP_WIDTH, self::LQIP_WIDTH, false );
		$editor->set_quality( self::LQIP_QUALITY );

		$tmp = trailingslashit( get_temp_dir() ) . 'nc_lqip_' . $attachment_id . '_' . wp_generate_password( 8, false ) . '.jpg';
		$saved = $editor->save( $tmp, 'image/jpeg' );

		if ( is_wp_error( $saved ) || ! file_exists( $tmp ) ) {
			return '';
		}

		$raw = file_get_contents( $tmp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		wp_delete_file( $tmp );

		if ( ! $raw ) {
			return '';
		}

		$data_uri = 'data:image/jpeg;base64,' . base64_encode( $raw ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode

		// Sanity check: if somehow the tiny version is still huge, skip it.
		if ( strlen( $data_uri ) > 6000 ) {
			return '';
		}

		update_post_meta( $attachment_id, self::META_KEY, $data_uri );

		return $data_uri;
	}

	/**
	 * Delete stored LQIP meta when an attachment is deleted.
	 *
	 * @param int $attachment_id Attachment post ID.
	 * @return void
	 */
	public function delete_lqip( $attachment_id ) {
		delete_post_meta( absint( $attachment_id ), self::META_KEY );
	}

	/* ── Front-end Filters ───────────────────────────────────────────────── */

	/**
	 * Add data-nc-lqip attribute to images rendered via wp_get_attachment_image().
	 *
	 * @param array    $attrs      Existing attributes.
	 * @param \WP_Post $attachment Attachment post object.
	 * @return array
	 */
	public function add_lqip_attrs( $attrs, $attachment ) {
		$lqip = get_post_meta( $attachment->ID, self::META_KEY, true );
		if ( ! $lqip ) {
			return $attrs;
		}

		$attrs['data-nc-lqip'] = $lqip;
		$attrs['class']        = trim( ( isset( $attrs['class'] ) ? $attrs['class'] : '' ) . ' nc-lqip' );

		return $attrs;
	}

	/**
	 * Add data-nc-lqip attribute to <img> tags inside post content.
	 *
	 * Identifies WP-added images via their wp-image-{ID} CSS class.
	 *
	 * @param string $content Post content.
	 * @return string
	 */
	public function add_lqip_to_content( $content ) {
		if ( ! $content || false === strpos( $content, '<img' ) ) {
			return $content;
		}

		return preg_replace_callback(
			'/<img(?:[^>]*?)>/is',
			array( $this, 'inject_lqip_into_tag' ),
			$content
		);
	}

	/**
	 * Inject LQIP attributes into a single <img> tag string.
	 *
	 * @param array $matches Regex matches (full tag in [0]).
	 * @return string Modified tag, or original on failure.
	 */
	protected function inject_lqip_into_tag( $matches ) {
		$tag = $matches[0];

		if ( false !== strpos( $tag, 'data-nc-lqip' ) ) {
			return $tag;
		}

		if ( ! preg_match( '/class=["\'][^"\']*wp-image-(\d+)[^"\']*["\']/', $tag, $id_match ) ) {
			return $tag;
		}

		$attachment_id = absint( $id_match[1] );
		$lqip          = get_post_meta( $attachment_id, self::META_KEY, true );

		if ( ! $lqip ) {
			return $tag;
		}

		// Add nc-lqip to existing class attribute.
		$tag = preg_replace(
			'/class=(["\'])([^"\']*)["\']/',
			'class=$1$2 nc-lqip$1',
			$tag,
			1
		);

		// Inject data-nc-lqip immediately after <img.
		$tag = preg_replace(
			'/<img(\s)/i',
			'<img data-nc-lqip="' . esc_attr( $lqip ) . '"$1',
			$tag,
			1
		);

		return $tag;
	}

	/* ── Frontend Assets ─────────────────────────────────────────────────── */

	/**
	 * Enqueue front-end CSS and JS for lazy-load blur effect.
	 *
	 * @return void
	 */
	public function enqueue_frontend() {
		if ( ! $this->settings->get( 'lqip_enabled' ) ) {
			return;
		}

		wp_enqueue_style(
			'naveencodes-lazyload',
			NAVEENCODES_URL . 'assets/css/lazyload.css',
			array(),
			NAVEENCODES_VERSION
		);

		wp_enqueue_script(
			'naveencodes-lazyload',
			NAVEENCODES_URL . 'assets/js/lazyload.js',
			array(),
			NAVEENCODES_VERSION,
			true
		);
	}
}
