<?php
/**
 * Spec for Block_Allowlist (L3 enforcement).
 *
 * @package Ppok\AIToolkit\Tests
 * @license GPL-2.0-or-later
 */

namespace Ppok\AIToolkit\Tests;

use Ppok\AIToolkit\Abilities\Block_Allowlist;
use Ppok\AIToolkit\Abilities\Scope_Gate;

class Test_Block_Allowlist extends \WP_UnitTestCase {

	protected function tearDown(): void {
		delete_option( Scope_Gate::OPTION_KEY );
		parent::tearDown();
	}

	private function paragraph_block( string $html = '<p>x</p>' ): array {
		return array(
			'blockName'    => 'core/paragraph',
			'attrs'        => array(),
			'innerBlocks'  => array(),
			'innerHTML'    => $html,
			'innerContent' => array( $html ),
		);
	}

	public function test_registered_mode_accepts_known_core_block(): void {
		delete_option( Scope_Gate::OPTION_KEY );
		$this->assertTrue( Block_Allowlist::check( array( $this->paragraph_block() ) ) );
	}

	public function test_registered_mode_rejects_unknown_block_with_structured_error(): void {
		delete_option( Scope_Gate::OPTION_KEY );
		$block = array(
			'blockName'    => 'no-such/block-type-12345',
			'attrs'        => array(),
			'innerBlocks'  => array(),
			'innerHTML'    => '',
			'innerContent' => array(),
		);
		$result = Block_Allowlist::check( array( $block ) );
		$this->assertInstanceOf( \WP_Error::class, $result );
		$this->assertSame( 'unknown_block', $result->get_error_code() );
		$data = $result->get_error_data();
		$this->assertSame( Block_Allowlist::MODE_REGISTERED, $data['mode'] );
		$this->assertSame( array( 'no-such/block-type-12345' ), $data['unknown_block_names'] );
	}

	public function test_walks_inner_blocks_recursively(): void {
		delete_option( Scope_Gate::OPTION_KEY );
		$tree = array(
			array(
				'blockName'    => 'core/group',
				'attrs'        => array(),
				'innerBlocks'  => array(
					$this->paragraph_block(),
					array(
						'blockName'    => 'made-up/block-deep',
						'attrs'        => array(),
						'innerBlocks'  => array(),
						'innerHTML'    => '',
						'innerContent' => array(),
					),
				),
				'innerHTML'    => '',
				'innerContent' => array(),
			),
		);
		$result = Block_Allowlist::check( $tree );
		$this->assertInstanceOf( \WP_Error::class, $result );
		$data = $result->get_error_data();
		$this->assertSame( array( 'made-up/block-deep' ), $data['unknown_block_names'] );
	}

	public function test_null_blockname_freeform_markup_is_always_allowed(): void {
		// parse_blocks() emits blockName=null for classic-editor markup;
		// rejecting it would prevent editing any post with legacy content.
		delete_option( Scope_Gate::OPTION_KEY );
		$freeform = array(
			'blockName'    => null,
			'attrs'        => array(),
			'innerBlocks'  => array(),
			'innerHTML'    => '<p>raw</p>',
			'innerContent' => array( '<p>raw</p>' ),
		);
		$this->assertTrue( Block_Allowlist::check( array( $freeform ) ) );
	}

	public function test_curated_mode_only_accepts_listed_names(): void {
		update_option(
			Scope_Gate::OPTION_KEY,
			array(
				'block_allowlist_mode'    => Block_Allowlist::MODE_CURATED,
				'block_curated_allowlist' => array( 'core/paragraph' ),
			)
		);

		$this->assertTrue( Block_Allowlist::check( array( $this->paragraph_block() ) ) );

		$heading = array(
			'blockName'    => 'core/heading',
			'attrs'        => array(),
			'innerBlocks'  => array(),
			'innerHTML'    => '<h2>x</h2>',
			'innerContent' => array( '<h2>x</h2>' ),
		);
		$result = Block_Allowlist::check( array( $heading ) );
		$this->assertInstanceOf( \WP_Error::class, $result );
		$data = $result->get_error_data();
		$this->assertSame( Block_Allowlist::MODE_CURATED, $data['mode'] );
		$this->assertSame( array( 'core/heading' ), $data['unknown_block_names'] );
	}

	public function test_dedupes_repeated_unknown_block_names(): void {
		delete_option( Scope_Gate::OPTION_KEY );
		$bad = array(
			'blockName'    => 'unknown/x',
			'attrs'        => array(),
			'innerBlocks'  => array(),
			'innerHTML'    => '',
			'innerContent' => array(),
		);
		$result = Block_Allowlist::check( array( $bad, $bad, $bad ) );
		$this->assertInstanceOf( \WP_Error::class, $result );
		$this->assertSame( array( 'unknown/x' ), $result->get_error_data()['unknown_block_names'] );
	}
}
