<?php
/**
 * System blocks REST controller.
 *
 * @package JOCBM
 */

namespace JOCBM\Rest;

use JOCBM\Database\Schema;
use JOCBM\Services\BlockCategories;
use JOCBM\Services\CssVars;
use JOCBM\Services\TemplateLibrary;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

defined( 'ABSPATH' ) || exit;

/**
 * Class SystemBlocksController
 */
final class SystemBlocksController {

	use RestPermissions;

	/**
	 * Register routes.
	 */
	public function register_routes(): void {
		register_rest_route(
			RestApi::NAMESPACE,
			'/system-blocks',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'index' ),
				'permission_callback' => array( $this, 'can_manage' ),
				'args'                => array(
					'category' => array(
						'type'              => 'string',
						'sanitize_callback' => 'sanitize_title',
					),
				),
			)
		);

		register_rest_route(
			RestApi::NAMESPACE,
			'/system-blocks/(?P<id>\d+)',
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'show' ),
				'permission_callback' => array( $this, 'can_manage' ),
			)
		);
	}

	/**
	 * List system blocks (DB core) + published Owner packs from disk.
	 *
	 * @param WP_REST_Request $request Request.
	 */
	public function index( WP_REST_Request $request ): WP_REST_Response {
		global $wpdb;

		$table      = Schema::table( 'system_blocks' );
		$cats       = Schema::table( 'categories' );
		$junction   = Schema::table( 'system_block_categories' );
		$category   = $request->get_param( 'category' );
		$owner_keys = TemplateLibrary::owner_pack_keys();
		$parent_map = BlockCategories::parent_map();

		if ( $category && 'featured' === $category ) {
			// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- %i table identifiers are not counted by PHPCS.
			$rows = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT b.*, c.slug AS category_slug, c.name AS category_name, c.parent_slug AS category_parent_slug
				FROM %i b
				INNER JOIN %i c ON c.id = b.category_id
				WHERE b.is_active = 1
				AND COALESCE(b.source, 'core') <> 'creator'
				AND b.is_featured = 1
				ORDER BY b.name ASC, b.variant ASC",
					$table,
					$cats
				),
				ARRAY_A
			);
		} elseif ( $category ) {
			// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- %i table identifiers are not counted by PHPCS.
			$rows = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT DISTINCT b.*, c.slug AS category_slug, c.name AS category_name, c.parent_slug AS category_parent_slug
				FROM %i b
				INNER JOIN %i c ON c.id = b.category_id
				INNER JOIN %i j ON j.system_block_id = b.id
				INNER JOIN %i jc ON jc.id = j.category_id
				WHERE b.is_active = 1
				AND COALESCE(b.source, 'core') <> 'creator'
				AND ( jc.slug = %s OR jc.parent_slug = %s )
				ORDER BY b.name ASC, b.variant ASC",
					$table,
					$cats,
					$junction,
					$cats,
					$category,
					$category
				),
				ARRAY_A
			);
		} else {
			// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- %i table identifiers are not counted by PHPCS.
			$rows = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT b.*, c.slug AS category_slug, c.name AS category_name, c.parent_slug AS category_parent_slug
				FROM %i b
				INNER JOIN %i c ON c.id = b.category_id
				WHERE b.is_active = 1
				AND COALESCE(b.source, 'core') <> 'creator'
				ORDER BY b.name ASC, b.variant ASC",
					$table,
					$cats
				),
				ARRAY_A
			);
		}

		$items = array();
		$seen  = array();
		$block_ids = array();
		foreach ( $rows ?: array() as $row ) {
			$block_ids[] = (int) $row['id'];
		}
		$slugs_by_id = BlockCategories::slugs_for_blocks( $block_ids );

		foreach ( $rows ?: array() as $row ) {
			$key = sanitize_key( (string) ( $row['slug'] ?? '' ) ) . '::' .
				( sanitize_key( (string) ( $row['variant'] ?? 'default' ) ) ?: 'default' );
			// Owner packs are listed from disk only (never trust a seeded ghost).
			if ( isset( $owner_keys[ $key ] ) ) {
				continue;
			}
			$items[]      = $this->format_block( $row, $slugs_by_id[ (int) $row['id'] ] ?? null );
			$seen[ $key ] = true;
		}

		foreach ( TemplateLibrary::ready_owner_blocks() as $block ) {
			$key = sanitize_key( (string) ( $block['slug'] ?? '' ) ) . '::' .
				( sanitize_key( (string) ( $block['variant'] ?? 'default' ) ) ?: 'default' );
			if ( isset( $seen[ $key ] ) ) {
				continue;
			}
			// Variants nest under their parent — never their own Library cards.
			if ( 'variant' === TemplateLibrary::resolve_pack_type( $block ) ) {
				continue;
			}
			if ( $category ) {
				if ( 'featured' === $category ) {
					if ( empty( $block['is_featured'] ) ) {
						continue;
					}
				} elseif ( ! BlockCategories::block_matches_filter(
					array(
						'categorySlug'       => (string) ( $block['category'] ?? '' ),
						'categorySlugs'      => is_array( $block['categories'] ?? null )
							? $block['categories']
							: array( (string) ( $block['category'] ?? '' ) ),
						'categoryParentSlug' => '',
					),
					(string) $category,
					$parent_map
				) ) {
					continue;
				}
			}
			$items[]      = $this->format_disk_block( $block );
			$seen[ $key ] = true;
		}

		$items = $this->attach_library_variants( $items );

		usort(
			$items,
			static function ( $a, $b ) {
				$by_name = strcmp( (string) ( $a['name'] ?? '' ), (string) ( $b['name'] ?? '' ) );
				if ( 0 !== $by_name ) {
					return $by_name;
				}
				return strcmp( (string) ( $a['variant'] ?? '' ), (string) ( $b['variant'] ?? '' ) );
			}
		);

		return new WP_REST_Response( array( 'items' => $items ), 200 );
	}

	/**
	 * Single system block.
	 *
	 * @param WP_REST_Request $request Request.
	 * @return WP_REST_Response|WP_Error
	 */
	public function show( WP_REST_Request $request ) {
		global $wpdb;

		$id    = (int) $request['id'];
		$table = Schema::table( 'system_blocks' );
		$cats  = Schema::table( 'categories' );

		$row = $wpdb->get_row(
			// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- %i table identifiers are not counted by PHPCS.
			$wpdb->prepare(
				"SELECT b.*, c.slug AS category_slug, c.name AS category_name, c.parent_slug AS category_parent_slug
				FROM %i b
				INNER JOIN %i c ON c.id = b.category_id
				WHERE b.id = %d",
				$table,
				$cats,
				$id
			),
			ARRAY_A
		);

		if ( ! $row ) {
			return new WP_Error( 'jocbm_not_found', __( 'Block not found.', 'joc-blockora-morph' ), array( 'status' => 404 ) );
		}

		return new WP_REST_Response( $this->format_block( $row ), 200 );
	}

	/**
	 * Format DB row.
	 *
	 * @param array<string, mixed>      $row   Row.
	 * @param array<int, string>|null   $slugs Optional preloaded category slugs.
	 * @return array<string, mixed>
	 */
	private function format_block( array $row, ?array $slugs = null ): array {
		$id    = (int) $row['id'];
		$slugs = null !== $slugs
			? array_values( array_filter( array_map( 'sanitize_key', $slugs ) ) )
			: BlockCategories::slugs_for_block( $id );
		if ( ! $slugs ) {
			$primary = sanitize_key( (string) ( $row['category_slug'] ?? '' ) );
			$slugs   = $primary ? array( $primary ) : array();
		}

		$formatted = array(
			'id'                 => $id,
			'categoryId'         => (int) $row['category_id'],
			'categorySlug'       => $row['category_slug'] ?? '',
			'categoryName'       => $row['category_name'] ?? '',
			'categoryParentSlug' => $row['category_parent_slug'] ?? '',
			'categorySlugs'      => $slugs,
			'slug'               => $row['slug'],
			'name'               => $row['name'],
			'description'        => $row['description'],
			'variant'            => $row['variant'],
			'thumbnail'          => $row['thumbnail'],
			'markup'             => $row['markup'],
			'cssVars'            => CssVars::normalize( json_decode( $row['css_vars'] ?: '{}', true ) ?: array() ),
			'defaultAttrs'       => json_decode( $row['default_attrs'] ?: '{}', true ) ?: array(),
			'fieldSchema'        => json_decode( $row['field_schema'] ?: '[]', true ) ?: array(),
			'styleSchema'        => json_decode( $row['style_schema'] ?: '[]', true ) ?: array(),
			'sortOrder'          => (int) $row['sort_order'],
			'isPro'              => ! empty( $row['is_pro'] ),
			'isFeatured'         => ! empty( $row['is_featured'] ),
			'source'             => sanitize_key( (string) ( $row['source'] ?? 'core' ) ) ?: 'core',
			'ownerPack'          => false,
			'packType'           => 'standalone',
			'extends'            => null,
			'variants'           => array(),
		);

		/**
		 * Filter a formatted system block for the REST API / Library UI.
		 *
		 * @param array<string, mixed> $formatted Formatted block.
		 * @param array<string, mixed> $row       Raw DB row.
		 */
		$filtered = apply_filters( 'jocbm_format_system_block', $formatted, $row );

		return is_array( $filtered ) ? $filtered : $formatted;
	}

	/**
	 * Format an Owner pack loaded from disk (no system_blocks row).
	 *
	 * @param array<string, mixed> $block TemplateLibrary block.
	 * @return array<string, mixed>
	 */
	private function format_disk_block( array $block ): array {
		global $wpdb;

		$slug    = sanitize_key( (string) ( $block['slug'] ?? '' ) );
		$variant = sanitize_key( (string) ( $block['variant'] ?? 'default' ) ) ?: 'default';
		$cat     = sanitize_key( (string) ( $block['category'] ?? '' ) );
		$slugs   = is_array( $block['categories'] ?? null )
			? array_values( array_filter( array_map( 'sanitize_key', $block['categories'] ) ) )
			: ( $cat ? array( $cat ) : array() );
		if ( $cat && ! in_array( $cat, $slugs, true ) ) {
			array_unshift( $slugs, $cat );
		}
		$cats    = Schema::table( 'categories' );
		$cat_row = $cat
			? $wpdb->get_row(
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Plugin tables from Schema::table().
				$wpdb->prepare(
					'SELECT id, slug, name, parent_slug FROM %i WHERE slug = %s',
					$cats,
					$cat
				),
				ARRAY_A
			)
			: null;

		// Stable negative id for React keys — import uses ownerSlug/ownerVariant.
		$id = -1 * ( (int) sprintf( '%u', crc32( $slug . '::' . $variant ) ) % 2000000000 );
		if ( 0 === $id ) {
			$id = -1;
		}

		$extends = ! empty( $block['extends'] ) && is_array( $block['extends'] )
			? $block['extends']
			: null;

		$formatted = array(
			'id'                 => $id,
			'categoryId'         => (int) ( $cat_row['id'] ?? 0 ),
			'categorySlug'       => (string) ( $cat_row['slug'] ?? $cat ),
			'categoryName'       => (string) ( $cat_row['name'] ?? ucwords( str_replace( '-', ' ', $cat ) ) ),
			'categoryParentSlug' => (string) ( $cat_row['parent_slug'] ?? '' ),
			'categorySlugs'      => $slugs,
			'slug'               => $slug,
			'name'               => (string) ( $block['name'] ?? '' ),
			'description'        => (string) ( $block['description'] ?? '' ),
			'variant'            => $variant,
			'thumbnail'          => '',
			'markup'             => (string) ( $block['markup'] ?? '' ),
			'cssVars'            => CssVars::normalize(
				is_array( $block['css_vars'] ?? null ) ? $block['css_vars'] : array()
			),
			'defaultAttrs'       => is_array( $block['default_attrs'] ?? null ) ? $block['default_attrs'] : array(),
			'fieldSchema'        => is_array( $block['field_schema'] ?? null ) ? $block['field_schema'] : array(),
			'styleSchema'        => is_array( $block['style_schema'] ?? null ) ? $block['style_schema'] : array(),
			'sortOrder'          => (int) ( $block['sort_order'] ?? 0 ),
			'isPro'              => ! empty( $block['is_pro'] ),
			'isFeatured'         => ! empty( $block['is_featured'] ),
			'source'             => sanitize_key( (string) ( $block['source'] ?? 'core' ) ) ?: 'core',
			'ownerPack'          => true,
			'ownerSlug'          => $slug,
			'ownerVariant'       => $variant,
			'publisherStatus'    => sanitize_key( (string) ( $block['publisher_status'] ?? 'ready' ) ),
			'packType'           => TemplateLibrary::resolve_pack_type( $block ),
			'extends'            => $extends,
			'variants'           => array(),
		);

		$filtered = apply_filters( 'jocbm_format_system_block', $formatted, $block );

		return is_array( $filtered ) ? $filtered : $formatted;
	}

	/**
	 * Nest ready Owner variant packs under matching parent Library cards.
	 *
	 * @param array<int, array<string, mixed>> $items Formatted library items.
	 * @return array<int, array<string, mixed>>
	 */
	private function attach_library_variants( array $items ): array {
		$buckets = array();
		foreach ( TemplateLibrary::ready_owner_blocks() as $block ) {
			if ( 'variant' !== TemplateLibrary::resolve_pack_type( $block ) ) {
				continue;
			}
			$extends = ! empty( $block['extends'] ) && is_array( $block['extends'] )
				? $block['extends']
				: null;
			if ( ! $extends ) {
				continue;
			}
			$parent_slug = sanitize_key( (string) ( $extends['slug'] ?? '' ) );
			$parent_var  = sanitize_key( (string) ( $extends['variant'] ?? 'default' ) ) ?: 'default';
			if ( '' === $parent_slug ) {
				continue;
			}
			$key               = $parent_slug . '::' . $parent_var;
			$buckets[ $key ][] = $this->format_disk_block( $block );
		}

		foreach ( $items as $i => $item ) {
			$slug = sanitize_key( (string) ( $item['slug'] ?? '' ) );
			$var  = sanitize_key( (string) ( $item['variant'] ?? 'default' ) ) ?: 'default';
			$key  = $slug . '::' . $var;
			$items[ $i ]['variants'] = isset( $buckets[ $key ] )
				? array_values( $buckets[ $key ] )
				: array();
			if ( empty( $items[ $i ]['packType'] ) ) {
				$items[ $i ]['packType'] = 'standalone';
			}
		}

		return $items;
	}
}
