<?php
/**
 * Block Catalog service (compact, cached)
 *
 * @package AgenticWP
 */

namespace Agentic_WP;

defined( 'ABSPATH' ) || exit;

/**
 * WordPress block catalog and information provider.
 *
 * Manages block type information, caching, and provides structured
 * data about available WordPress blocks for AI agent consumption.
 *
 * @since 1.0.0
 */
class Block_Catalog {

	/**
	 * Transient key for cached block cards data.
	 *
	 * @since 1.0.0
	 * @var string
	 */
	private const CARDS_TRANSIENT = 'agenticwp_block_cards_v1';

	/**
	 * Transient key prefix for individual block information.
	 *
	 * @since 1.0.0
	 * @var string
	 */
	private const INFO_TRANSIENT_PREFIX = 'agenticwp_block_info_';

	/**
	 * Cache time-to-live in seconds (12 hours).
	 *
	 * @since 1.0.0
	 * @var int
	 */
	private const CACHE_TTL = 12 * HOUR_IN_SECONDS;

	/**
	 * Lists available blocks with filtering and pagination.
	 *
	 * @since 1.0.0
	 *
	 * @param array $args Optional. Query arguments for filtering blocks.
	 * @return array Paginated block list with items, total, page, and per_page.
	 */
	public static function list_blocks( array $args = array() ): array {
		$search   = isset( $args['search'] ) && is_string( $args['search'] ) ? strtolower( $args['search'] ) : '';
		$category = isset( $args['category'] ) && is_string( $args['category'] ) ? strtolower( $args['category'] ) : '';
		$per_page = isset( $args['per_page'] ) && is_int( $args['per_page'] ) ? max( 1, min( 100, $args['per_page'] ) ) : 20;
		$page     = isset( $args['page'] ) && is_int( $args['page'] ) ? max( 1, $args['page'] ) : 1;

		$cards = self::get_all_cards();

		if ( '' !== $search || '' !== $category ) {
			$cards = array_values(
				array_filter(
					$cards,
					function ( $card ) use ( $search, $category ) {
						if ( '' !== $category ) {
							$c = isset( $card['category'] ) ? strtolower( (string) $card['category'] ) : '';
							if ( $c !== $category ) {
								return false;
							}
						}
						if ( '' !== $search ) {
							$hay = strtolower( ( $card['slug'] ?? '' ) . ' ' . ( $card['title'] ?? '' ) );
							if ( strpos( $hay, $search ) === false ) {
								return false;
							}
						}
						return true;
					}
				)
			);
		}

		$total = count( $cards );
		$start = ( $page - 1 ) * $per_page;
		$items = array_slice( $cards, $start, $per_page );

		return array(
			'items'    => array_values( $items ),
			'total'    => (int) $total,
			'page'     => (int) $page,
			'per_page' => (int) $per_page,
		);
	}

	/**
	 * Retrieves detailed block type information.
	 *
	 * @since 1.0.0
	 *
	 * @param string $slug Block slug to get information for.
	 * @return array Block information including attributes and example.
	 */
	public static function get_block_info( string $slug ): array {
		$slug = trim( $slug );
		if ( '' === $slug ) {
			return array();
		}

		$cache_key = self::INFO_TRANSIENT_PREFIX . md5( strtolower( $slug ) );
		$cached    = get_transient( $cache_key );
		if ( is_array( $cached ) ) {
			return $cached;
		}

		$registry = \WP_Block_Type_Registry::get_instance();
		$type     = $registry->get_registered( $slug );
		if ( ! $type ) {
			return array();
		}

		$attrs = array();
		if ( isset( $type->attributes ) && is_array( $type->attributes ) ) {
			foreach ( $type->attributes as $name => $schema ) {
				$norm = array( 'name' => (string) $name );
				$t    = 'string';
				if ( is_array( $schema ) && isset( $schema['type'] ) ) {
					// Handle array types (e.g., ['string', 'null']) by taking first type.
					if ( is_array( $schema['type'] ) ) {
						$t = is_string( $schema['type'][0] ?? '' ) ? $schema['type'][0] : 'string';
					} elseif ( is_string( $schema['type'] ) ) {
						$t = $schema['type'];
					}
				}
				$norm['type'] = self::normalize_type( $t );
				if ( is_array( $schema ) ) {
					if ( isset( $schema['enum'] ) && is_array( $schema['enum'] ) ) {
						$enum = array_values( $schema['enum'] );
						if ( count( $enum ) <= 8 ) {
							$norm['enum'] = $enum;
						}
					}
					if ( array_key_exists( 'default', $schema ) ) {
						$def = $schema['default'];
						if ( is_scalar( $def ) || null === $def ) {
							$norm['default'] = $def;
						}
					}
				}
				$attrs[] = $norm;
			}
		}

		$info = array(
			'slug'           => (string) $type->name,
			'title'          => (string) ( $type->title ?? $type->name ),
			'attrs'          => $attrs,
			'hasInnerBlocks' => self::maybe_has_inner_blocks( $type ),
			'example'        => self::make_minimal_example( (string) $type->name ),
		);

		set_transient( $cache_key, $info, self::CACHE_TTL );
		return $info;
	}

	/**
	 * Gets all block cards from cache or builds them.
	 *
	 * @since 1.0.0
	 *
	 * @return array Array of block cards with slug, title, category, and attributes.
	 */
	private static function get_all_cards(): array {
		$cached = get_transient( self::CARDS_TRANSIENT );
		if ( is_array( $cached ) ) {
			return $cached;
		}

		$registry = \WP_Block_Type_Registry::get_instance();
		$all      = $registry->get_all_registered();
		$cards    = array();
		foreach ( $all as $slug => $type ) {
			$title     = (string) ( $type->title ?? $slug );
			$category  = isset( $type->category ) ? (string) $type->category : '';
			$top_attrs = array();
			if ( isset( $type->attributes ) && is_array( $type->attributes ) ) {
				$names     = array_keys( $type->attributes );
				$top_attrs = array_slice( array_map( 'strval', $names ), 0, 4 );
			}
			$cards[] = array(
				'slug'      => (string) $slug,
				'title'     => $title,
				'category'  => $category,
				'top_attrs' => $top_attrs,
			);
		}

		set_transient( self::CARDS_TRANSIENT, $cards, self::CACHE_TTL );
		return $cards;
	}

	/**
	 * Normalizes attribute type names to standard types.
	 *
	 * @since 1.0.0
	 *
	 * @param string $t Type name to normalize.
	 * @return string Normalized type (string, number, boolean, array, object).
	 */
	private static function normalize_type( string $t ): string {
		$t = strtolower( $t );
		if ( 'integer' === $t ) {
			return 'number';
		}
		$allowed = array( 'string', 'number', 'boolean', 'array', 'object' );
		return in_array( $t, $allowed, true ) ? $t : 'string';
	}

	/**
	 * Checks if block type supports inner blocks.
	 *
	 * @since 1.0.0
	 *
	 * @param mixed $type Block type object.
	 * @return bool True if block supports nesting, false otherwise.
	 */
	private static function maybe_has_inner_blocks( $type ): bool {
		$containers = array( 'core/group', 'core/cover', 'core/columns', 'core/column', 'core/media-text' );
		if ( isset( $type->name ) && in_array( (string) $type->name, $containers, true ) ) {
			return true;
		}
		return false;
	}

	/**
	 * Creates minimal block example.
	 *
	 * @since 1.0.0
	 *
	 * @param string $slug Block slug.
	 * @return string Serialized minimal block example.
	 */
	private static function make_minimal_example( string $slug ): string {
		$block = array(
			'blockName'    => $slug,
			'attrs'        => array(),
			'innerBlocks'  => array(),
			'innerHTML'    => '',
			'innerContent' => array(),
		);
		return serialize_block( $block );
	}
}
