<?php
/**
 * Sync attachment alt text into post content.
 *
 * @package AIAltTextGenerator
 * @since 1.6.0
 */

namespace AIAltTextGenerator;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * @since 1.6.0
 */
class ContentSync {

	/**
	 * Sync alt text into posts/pages that reference the attachment.
	 *
	 * Authorization: When called with $privileged_context = false (default),
	 * only posts the current user can edit will be updated. Use $privileged_context = true
	 * only from explicitly authorized background jobs (e.g., admin-initiated cron tasks).
	 *
	 * @param int    $attachment_id     The attachment ID.
	 * @param string $alt_text          The alt text to sync.
	 * @param bool   $privileged_context Whether running in a privileged context (skip per-post auth).
	 * @return int Number of posts updated.
	 */
	public static function syncAttachmentAlt( int $attachment_id, string $alt_text, bool $privileged_context = false ): int {
		$settings = Settings::get();
		if ( empty( $settings['sync_content_alt'] ) ) {
			return 0;
		}

		$post_ids = AttachmentUsage::findPostIds( $attachment_id );
		$updated  = 0;

		foreach ( $post_ids as $post_id ) {
			if ( self::updatePostAlt( $post_id, $attachment_id, $alt_text, $privileged_context ) ) {
				++$updated;
			}
		}

		if ( class_exists( ElementorIntegration::class ) ) {
			$updated += ElementorIntegration::syncAltForAttachment( $attachment_id, $alt_text, $privileged_context );
		}

		return $updated;
	}

	/**
	 * Push library alt into content regardless of sync_content_alt setting.
	 *
	 * Authorization: When called with $privileged_context = false (default),
	 * only posts the current user can edit will be updated. Use $privileged_context = true
	 * only from explicitly authorized background jobs (e.g., admin-initiated cron tasks).
	 *
	 * @param int    $attachment_id     The attachment ID.
	 * @param string $alt_text          The alt text to push.
	 * @param bool   $privileged_context Whether running in a privileged context (skip per-post auth).
	 * @return int Number of posts updated.
	 */
	public static function pushLibraryAlt( int $attachment_id, string $alt_text, bool $privileged_context = false ): int {
		$post_ids = AttachmentUsage::findPostIds( $attachment_id );
		$updated  = 0;

		foreach ( $post_ids as $post_id ) {
			if ( self::updatePostAlt( $post_id, $attachment_id, $alt_text, $privileged_context ) ) {
				++$updated;
			}
		}

		if ( class_exists( ElementorIntegration::class ) ) {
			$updated += ElementorIntegration::syncAltForAttachmentForced( $attachment_id, $alt_text, $privileged_context );
		}

		return $updated;
	}

	/**
	 * Update alt text in a post's content.
	 *
	 * Authorization check: requires the current user to have edit_post capability
	 * for the destination post. This prevents authors from modifying content in
	 * posts they don't have permission to edit via attachment alt-text sync.
	 *
	 * @param int    $post_id       The post ID to update.
	 * @param int    $attachment_id The attachment ID whose alt text is being synced.
	 * @param string $alt_text      The new alt text.
	 * @param bool   $skip_auth     Whether to skip authorization (for background/cron jobs with explicit authorization).
	 * @return bool Whether the post was updated.
	 */
	private static function updatePostAlt( int $post_id, int $attachment_id, string $alt_text, bool $skip_auth = false ): bool {
		$post = get_post( $post_id );
		if ( ! $post instanceof \WP_Post ) {
			return false;
		}

		// Authorization check: ensure the current user can edit the destination post.
		// This prevents privilege escalation where an author modifies editor/admin content.
		if ( ! $skip_auth && ! current_user_can( 'edit_post', $post_id ) ) {
			return false;
		}

		$changed = false;

		if ( has_blocks( $post->post_content ) ) {
			$blocks  = parse_blocks( $post->post_content );
			$blocks  = self::updateBlocksAlt( $blocks, $attachment_id, $alt_text, $changed );
			if ( $changed ) {
				wp_update_post(
					[
						'ID'           => $post_id,
						'post_content' => serialize_blocks( $blocks ),
					]
				);
			}
		} else {
			$new_content = self::updateClassicHtmlAlt( $post->post_content, $attachment_id, $alt_text );
			if ( $new_content !== $post->post_content ) {
				wp_update_post(
					[
						'ID'           => $post_id,
						'post_content' => $new_content,
					]
				);
				$changed = true;
			}
		}

		return $changed;
	}

	/**
	 * @param list<array<string, mixed>> $blocks
	 * @return list<array<string, mixed>>
	 */
	private static function updateBlocksAlt( array $blocks, int $attachment_id, string $alt_text, bool &$changed ): array {
		foreach ( $blocks as &$block ) {
			if ( ! is_array( $block ) ) {
				continue;
			}

			$name = (string) ( $block['blockName'] ?? '' );
			$attrs = $block['attrs'] ?? [];

			if ( in_array( $name, [ 'core/image', 'core/cover', 'core/media-text' ], true ) ) {
				$id = (int) ( $attrs['id'] ?? 0 );
				if ( $id === $attachment_id ) {
					$current = (string) ( $attrs['alt'] ?? '' );
					if ( $current === '' || ! empty( Settings::get()['sync_overwrite_content_alt'] ) ) {
						$block['attrs']['alt'] = $alt_text;
						$changed               = true;
					}
				}
			}

			if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
				$block['innerBlocks'] = self::updateBlocksAlt( $block['innerBlocks'], $attachment_id, $alt_text, $changed );
			}
		}
		unset( $block );

		return $blocks;
	}

	private static function updateClassicHtmlAlt( string $content, int $attachment_id, string $alt_text ): string {
		$overwrite = ! empty( Settings::get()['sync_overwrite_content_alt'] );
		$class_id  = 'wp-image-' . $attachment_id;

		return (string) preg_replace_callback(
			'/<img\b[^>]*\bclass=["\'][^"\']*\b' . preg_quote( $class_id, '/' ) . '\b[^"\']*["\'][^>]*>/i',
			static function ( array $matches ) use ( $alt_text, $overwrite ): string {
				$tag = $matches[0];
				if ( preg_match( '/\balt=["\']([^"\']*)["\']/', $tag, $alt_match ) ) {
					if ( ! $overwrite && trim( $alt_match[1] ) !== '' ) {
						return $tag;
					}
					return (string) preg_replace( '/\balt=["\'][^"\']*["\']/', 'alt="' . esc_attr( $alt_text ) . '"', $tag );
				}
				return str_replace( '<img', '<img alt="' . esc_attr( $alt_text ) . '"', $tag );
			},
			$content
		);
	}
}
