<?php
namespace AutoSetFeaturedImage;

use WP_PowerSuite\Core\AbstractModule;
use WP_Post;
use WP_REST_Request;

class Module extends AbstractModule {

	private const MODULE_ID      = 'auto-set-featured-image';
	private const MODULE_VERSION = '1.0.1';
	private const APPLY_BATCH_MAX = 200;
	private const SCAN_BATCH_MAX  = 50;

	/** @var array{post_types: list<string>}|null */
	private ?array $settings_cache = null;

	public function getId(): string {
		return self::MODULE_ID;
	}

	public function getName(): string {
		return __( 'Auto Set Featured Image', 'wp-powersuite' );
	}

	public function getVersion(): string {
		return self::MODULE_VERSION;
	}

	public function getDescription(): string {
		return __( 'Promotes the first in-article image to featured image when none is chosen.', 'wp-powersuite' );
	}

	public function getContext(): string|array {
		return [ 'admin', 'ajax' ];
	}

	protected function registerHooks(): void {
		$post_types = $this->getEnabledPostTypes();
		if ( $post_types === [] ) {
			return;
		}

		foreach ( $post_types as $post_type ) {
			add_action( "save_post_{$post_type}", [ $this, 'maybeSetFeaturedImage' ], 20, 2 );
			add_action( "rest_after_insert_{$post_type}", [ $this, 'maybeSetFeaturedImageRest' ], 10, 3 );
		}

		add_action( 'wp_ajax_wpps_asfi_scan', [ $this, 'ajaxScan' ] );
		add_action( 'wp_ajax_wpps_asfi_apply', [ $this, 'ajaxApply' ] );
	}

	public function maybeSetFeaturedImage( int $post_id, mixed $post ): void {
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return;
		}

		if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
			return;
		}

		if ( ! $post instanceof WP_Post ) {
			$post = get_post( $post_id );
		}

		if ( ! $post instanceof WP_Post ) {
			return;
		}

		$this->setFeaturedImageIfNeeded( $post );
	}

	public function maybeSetFeaturedImageRest( WP_Post $post, WP_REST_Request $request, bool $creating ): void {
		unset( $request, $creating );

		if ( wp_is_post_revision( $post->ID ) || wp_is_post_autosave( $post->ID ) ) {
			return;
		}

		$this->setFeaturedImageIfNeeded( $post );
	}

	private function setFeaturedImageIfNeeded( WP_Post $post ): void {
		if ( ! $this->isEnabledPostType( $post->post_type ) || ! post_type_supports( $post->post_type, 'thumbnail' ) ) {
			return;
		}

		if ( ! current_user_can( 'edit_post', $post->ID ) ) {
			return;
		}

		if ( (int) get_post_thumbnail_id( $post->ID ) > 0 ) {
			return;
		}

		$first_image_id = $this->getFirstImageFromContent( $post->post_content );
		if ( $first_image_id <= 0 ) {
			return;
		}

		set_post_thumbnail( $post->ID, $first_image_id );
	}

	private function getFirstImageFromContent( string $content ): int {
		foreach ( $this->extractImageIdsFromContent( $content ) as $id ) {
			$id = (int) $id;
			if ( $id > 0 && $this->isImageAttachment( $id ) ) {
				return $id;
			}
		}

		return 0;
	}

	private function isImageAttachment( int $id ): bool {
		if ( get_post_type( $id ) !== 'attachment' ) {
			return false;
		}

		$mime = get_post_mime_type( $id );

		return is_string( $mime ) && str_starts_with( $mime, 'image/' );
	}

	private function extractImageIdsFromContent( string $content ): array {
		if ( $content === '' ) {
			return [];
		}

		$ids = [];
		if ( function_exists( 'parse_blocks' ) ) {
			$ids = $this->collectImageIdsFromBlocks( parse_blocks( $content ) );
		}

		if ( preg_match_all( '/\[gallery\s+ids=["\']([^"\']+)["\']/', $content, $matches ) ) {
			foreach ( $matches[1] as $ids_str ) {
				$parts = array_map( 'intval', array_filter( array_map( 'trim', explode( ',', $ids_str ) ) ) );
				$ids   = array_merge( $ids, $parts );
			}
		}

		if ( preg_match_all( '/wp-image-(\d+)/', $content, $matches ) ) {
			$ids = array_merge( $ids, array_map( 'intval', $matches[1] ) );
		}

		if ( $ids === [] && preg_match_all( '/<img[^>]+src=["\']([^"\']+)["\'][^>]*>/i', $content, $matches ) ) {
			foreach ( $matches[1] as $url ) {
				$attachment_id = $this->attachmentIdFromImgUrl( $url );
				if ( $attachment_id > 0 ) {
					$ids[] = $attachment_id;
				}
			}
		}

		return array_values( array_unique( array_filter( array_map( 'intval', $ids ) ) ) );
	}

	private function attachmentIdFromImgUrl( string $url ): int {
		if ( $url === '' || str_starts_with( $url, 'data:' ) ) {
			return 0;
		}

		$candidates = [ $url ];
		$stripped   = preg_replace( '/-\d+x\d+(\.[a-z]{3,4})$/i', '$1', $url );
		if ( is_string( $stripped ) && $stripped !== $url ) {
			$candidates[] = $stripped;
		}
		$scaled = preg_replace( '/-scaled(\.[a-z]{3,4})$/i', '$1', $url );
		if ( is_string( $scaled ) && $scaled !== $url && ! in_array( $scaled, $candidates, true ) ) {
			$candidates[] = $scaled;
		}

		foreach ( $candidates as $candidate ) {
			$attachment_id = (int) attachment_url_to_postid( $candidate );
			if ( $attachment_id > 0 ) {
				return $attachment_id;
			}
		}

		return 0;
	}

	private function collectImageIdsFromBlocks( array $blocks ): array {
		$ids = [];
		foreach ( $blocks as $block ) {
			$name  = $block['blockName'] ?? '';
			$attrs = $block['attrs'] ?? [];

			if ( $name === 'core/image' ) {
				if ( ! empty( $attrs['id'] ) ) {
					$ids[] = (int) $attrs['id'];
				} elseif ( ! empty( $attrs['url'] ) ) {
					$attachment_id = $this->attachmentIdFromImgUrl( (string) $attrs['url'] );
					if ( $attachment_id > 0 ) {
						$ids[] = $attachment_id;
					}
				}
			} elseif ( $name === 'core/gallery' && ! empty( $attrs['ids'] ) && is_array( $attrs['ids'] ) ) {
				foreach ( $attrs['ids'] as $id ) {
					$ids[] = (int) $id;
				}
			} elseif ( $name === 'core/media-text' && ! empty( $attrs['mediaId'] ) ) {
				$ids[] = (int) $attrs['mediaId'];
			} elseif ( $name === 'core/cover' && ! empty( $attrs['id'] ) ) {
				$ids[] = (int) $attrs['id'];
			}

			if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
				$ids = array_merge( $ids, $this->collectImageIdsFromBlocks( $block['innerBlocks'] ) );
			}
		}

		return $ids;
	}

	public function ajaxScan(): void {
		check_ajax_referer( 'wpps_asfi_scan', 'nonce' );
		if ( ! current_user_can( 'edit_posts' ) ) {
			wp_send_json_error( [ 'message' => __( 'You do not have permission.', 'wp-powersuite' ) ] );
		}

		$offset = isset( $_POST['offset'] ) ? max( 0, (int) $_POST['offset'] ) : 0;
		wp_send_json_success( $this->scanPostsWithoutFeaturedImages( $offset ) );
	}

	public function ajaxApply(): void {
		check_ajax_referer( 'wpps_asfi_apply', 'nonce' );
		if ( ! current_user_can( 'edit_posts' ) ) {
			wp_send_json_error( [ 'message' => __( 'You do not have permission.', 'wp-powersuite' ) ] );
		}

		$post_ids = [];
		if ( isset( $_POST['post_ids'] ) && is_array( $_POST['post_ids'] ) ) {
			$post_ids = array_map( 'absint', wp_unslash( $_POST['post_ids'] ) );
			$post_ids = array_values( array_filter( $post_ids ) );
		} elseif ( isset( $_POST['post_id'] ) ) {
			$post_ids = [ absint( wp_unslash( $_POST['post_id'] ) ) ];
		}

		if ( $post_ids === [] ) {
			wp_send_json_error( [ 'message' => __( 'No posts selected.', 'wp-powersuite' ) ] );
		}

		if ( count( $post_ids ) > self::APPLY_BATCH_MAX ) {
			wp_send_json_error( [
				'message' => sprintf(
					/* translators: %d: max posts per request */
					__( 'Too many posts at once. Maximum %d per request.', 'wp-powersuite' ),
					self::APPLY_BATCH_MAX
				),
			] );
		}

		$success = 0;
		$failed  = [];
		foreach ( $post_ids as $post_id ) {
			$result = $this->applyToPost( $post_id );
			if ( ! empty( $result['success'] ) ) {
				++$success;
			} else {
				$failed[] = [
					'post_id' => $post_id,
					'message' => $result['message'] ?? __( 'Unknown error.', 'wp-powersuite' ),
				];
			}
		}

		wp_send_json_success( [
			'success' => $success,
			'failed'  => $failed,
			'total'   => count( $post_ids ),
		] );
	}

	private function scanPostsWithoutFeaturedImages( int $offset ): array {
		$post_types = $this->getEnabledPostTypes();
		if ( $post_types === [] ) {
			return [
				'total'        => 0,
				'with_images'  => 0,
				'post_ids'     => [],
				'offset'       => 0,
				'next_offset'  => null,
				'done'         => true,
			];
		}

		global $wpdb;
		$placeholders = implode( ',', array_fill( 0, count( $post_types ), '%s' ) );
		$statuses     = [ 'publish', 'draft', 'pending', 'private', 'future' ];
		$status_sql   = implode( ',', array_fill( 0, count( $statuses ), '%s' ) );

		$base_args = array_merge( $post_types, $statuses );
		$sql_where = "p.post_type IN ($placeholders)
			AND p.post_status IN ($status_sql)
			AND NOT EXISTS (
				SELECT 1 FROM {$wpdb->postmeta} pm
				WHERE pm.post_id = p.ID
				AND pm.meta_key = '_thumbnail_id'
				AND pm.meta_value != ''
				AND pm.meta_value != '0'
			)";

		$total = (int) $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT(p.ID) FROM {$wpdb->posts} p WHERE {$sql_where}",
				...$base_args
			)
		);

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$candidate_ids = array_map(
			'intval',
			(array) $wpdb->get_col(
				$wpdb->prepare(
					"SELECT p.ID FROM {$wpdb->posts} p
					WHERE {$sql_where}
					ORDER BY p.ID ASC
					LIMIT %d OFFSET %d",
					...array_merge( $base_args, [ self::SCAN_BATCH_MAX, $offset ] )
				)
			)
		);

		$with_images = [];
		foreach ( $candidate_ids as $post_id ) {
			if ( $post_id <= 0 || ! current_user_can( 'edit_post', $post_id ) ) {
				continue;
			}

			$content = get_post_field( 'post_content', $post_id );
			if ( $this->getFirstImageFromContent( is_string( $content ) ? $content : '' ) > 0 ) {
				$with_images[] = $post_id;
			}
		}

		$next_offset = count( $candidate_ids ) < self::SCAN_BATCH_MAX ? null : $offset + self::SCAN_BATCH_MAX;

		return [
			'total'       => $total,
			'with_images' => count( $with_images ),
			'post_ids'    => $with_images,
			'offset'      => $offset,
			'next_offset' => $next_offset,
			'done'        => $next_offset === null,
		];
	}

	private function applyToPost( int $post_id ): array {
		$post = get_post( $post_id );
		if ( ! $post instanceof WP_Post || ! post_type_supports( $post->post_type, 'thumbnail' ) ) {
			return [ 'success' => false, 'message' => __( 'Invalid post or post type does not support thumbnails.', 'wp-powersuite' ) ];
		}

		if ( ! $this->isEnabledPostType( $post->post_type ) ) {
			return [ 'success' => false, 'message' => __( 'This post type is not enabled for this module.', 'wp-powersuite' ) ];
		}

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return [ 'success' => false, 'message' => __( 'You cannot edit this post.', 'wp-powersuite' ) ];
		}

		if ( (int) get_post_thumbnail_id( $post_id ) > 0 ) {
			return [ 'success' => false, 'message' => __( 'Post already has a featured image.', 'wp-powersuite' ) ];
		}

		$content  = get_post_field( 'post_content', $post_id );
		$image_id = $this->getFirstImageFromContent( is_string( $content ) ? $content : '' );
		if ( $image_id <= 0 ) {
			return [ 'success' => false, 'message' => __( 'No usable image found in content.', 'wp-powersuite' ) ];
		}

		$result = set_post_thumbnail( $post_id, $image_id );
		if ( is_wp_error( $result ) ) {
			return [ 'success' => false, 'message' => $result->get_error_message() ];
		}

		return $result
			? [ 'success' => true ]
			: [ 'success' => false, 'message' => __( 'Failed to set featured image.', 'wp-powersuite' ) ];
	}

	/**
	 * @return list<string>
	 */
	public static function getAllowedPostTypes(): array {
		$excluded = [ 'attachment', 'revision', 'nav_menu_item', 'custom_css', 'customize_changeset', 'oembed_cache', 'user_request', 'wp_block' ];

		return array_values(
			array_filter(
				get_post_types( [ 'public' => true ], 'names' ),
				static function ( string $post_type ) use ( $excluded ): bool {
					return ! in_array( $post_type, $excluded, true ) && post_type_supports( $post_type, 'thumbnail' );
				}
			)
		);
	}

	/**
	 * @return list<string>
	 */
	private function getEnabledPostTypes(): array {
		return $this->getSettings()['post_types'];
	}

	private function isEnabledPostType( string $post_type ): bool {
		$settings = $this->getSettings();

		return isset( $settings['lookup'][ $post_type ] );
	}

	private function getSettings(): array {
		if ( $this->settings_cache !== null ) {
			return $this->settings_cache;
		}

		$raw  = $this->moduleSettings()->all( [] );
		$raw  = is_array( $raw ) ? $raw : [];
		$allow = array_flip( self::getAllowedPostTypes() );
		$types = isset( $raw['post_types'] ) && is_array( $raw['post_types'] )
			? array_values( array_filter( array_map( 'sanitize_key', $raw['post_types'] ) ) )
			: [ 'post', 'page' ];

		$types = array_values(
			array_filter(
				$types,
				static fn( string $slug ): bool => $slug !== '' && isset( $allow[ $slug ] )
			)
		);

		if ( $types === [] ) {
			foreach ( [ 'post', 'page' ] as $fallback ) {
				if ( isset( $allow[ $fallback ] ) ) {
					$types[] = $fallback;
				}
			}
		}

		$this->settings_cache = [
			'post_types' => $types,
			'lookup'     => array_flip( $types ),
		];

		return $this->settings_cache;
	}
}
