<?php

/**
 * Trusted-addon API for the Block Builder library.
 *
 * @package LolaCore\BlockBuilder
 * @license GPL-2.0-or-later
 */

declare(strict_types=1);

namespace LolaCore\BlockBuilder;

use LolaCore\BlockBuilder\Admin\LibraryColumns;
use LolaCore\BlockBuilder\Gate\BlockBuilderGate;
use LolaCore\BlockBuilder\Handler\BlockBuilderHandler;
use LolaCore\BlockBuilder\PostType\BlockPostType;

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

// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Diagnostic exception messages; escaped at REST/chat presentation layer.

/**
 * PHP service boundary for addons orchestrating custom blocks (FS-S4).
 *
 * Addons must not call BlockBuilderHandler or Builder internals directly.
 * Literal file writes are not exposed here — use structured specs only.
 */
final class BlockBuilderService {

	public const MIN_LOLACORE_VERSION = '2.0.0';

	public const DEFAULT_CALLER = 'lolacore-for-fse';

	private BlockBuilderHandler $handler;

	public function __construct( ?BlockBuilderHandler $handler = null ) {
		$this->handler = $handler ?? new BlockBuilderHandler();
	}

	public static function isAvailable(): bool {
		return defined('LOLACORE_VERSION')
			&& version_compare((string) constant('LOLACORE_VERSION'), self::MIN_LOLACORE_VERSION, '>=')
			&& class_exists(BlockBuilderHandler::class);
	}

	/**
	 * @param array<string, mixed> $spec
	 * @return array<string, mixed>
	 *
	 * @throws \Exception When prerequisites fail or the handler rejects the spec.
	 */
	public function createFromSpec( array $spec, bool $dryRun = false, string $caller = self::DEFAULT_CALLER ): array {
		$this->assertCallable($caller);

		return $this->handler->handle(
			'create_block',
			array(
				'__lc_source'         => 'chat',
				'__lc_service_caller' => $caller,
				'spec'                => $spec,
			),
			$dryRun
		);
	}

	/**
	 * @param array<string, mixed> $spec
	 * @return array<string, mixed>
	 *
	 * @throws \Exception When prerequisites fail or the handler rejects the spec.
	 */
	public function updateFromSpec( int $postId, array $spec, bool $dryRun = false, string $caller = self::DEFAULT_CALLER ): array {
		$this->assertCallable($caller);

		return $this->handler->handle(
			'update_block_code',
			array(
				'__lc_source'         => 'chat',
				'__lc_service_caller' => $caller,
				'post_id'             => $postId,
				'spec'                => $spec,
			),
			$dryRun
		);
	}

	/**
	 * @param array<string, mixed> $params
	 * @return array<string, mixed>
	 *
	 * @throws \Exception When prerequisites fail or the handler rejects the request.
	 */
	public function listBlocks( array $params = array(), string $caller = self::DEFAULT_CALLER ): array {
		$this->assertCallable($caller);

		return $this->handler->handle(
			'list_built_blocks',
			array(
				'__lc_service_caller' => $caller,
			) + $params,
			false
		);
	}

	/**
	 * @return array<string, mixed>
	 *
	 * @throws \Exception When prerequisites fail or the post is missing.
	 */
	public function getBuiltBlock( int $postId, string $caller = self::DEFAULT_CALLER ): array {
		$this->assertCallable($caller);

		return $this->handler->handle(
			'get_built_block',
			array(
				'post_id'             => $postId,
				'__lc_service_caller' => $caller,
			),
			false
		);
	}

	/**
	 * @return array<string, mixed>
	 *
	 * @throws \Exception When prerequisites fail or the post is missing.
	 */
	public function getInstalledState( int $postId, string $caller = self::DEFAULT_CALLER ): array {
		$this->assertCallable($caller);

		return $this->handler->handle(
			'get_installed_block',
			array(
				'post_id'             => $postId,
				'__lc_service_caller' => $caller,
			),
			false
		);
	}

	/**
	 * @return array{gate_status: string, install_allowed: bool, attested_version: int, current_version: int}
	 */
	public function gateFlags( int $postId ): array {
		$this->assertCallable(self::DEFAULT_CALLER);

		return BlockBuilderGate::uiFlags($postId);
	}

	/**
	 * Whether a built block can be inserted into FSE layers via structured markup.
	 *
	 * @return array{
	 *     ready: bool,
	 *     missing_step: ?string,
	 *     message: string,
	 *     gate: array<string, mixed>,
	 *     block_name?: string,
	 *     post_id?: int
	 * }
	 */
	public function getPlacementReadiness( int $postId ): array {
		$this->assertCallable(self::DEFAULT_CALLER);
		$this->assertBlockPost($postId);

		$flags = BlockBuilderGate::uiFlags($postId);
		$state = $this->getInstalledState($postId);

		$block_name = (string) ( $state['block_name'] ?? '' );

		if (! ( $state['installed'] ?? false )) {
			if (! BlockBuilderGate::isInstallAllowed($postId)) {
				return array(
					'ready'         => false,
					'missing_step'  => 'test_block',
					'message'       => __(
						'Test this block in Playground and confirm it works before placing it in a template or template part.',
						'lolacore'
					),
					'gate'          => $flags,
					'block_name'    => $block_name,
					'post_id'       => $postId,
				);
			}

			return array(
				'ready'        => false,
				'missing_step' => 'install_block',
				'message'      => __(
					'Install this block on the site before placing it in a template or template part.',
					'lolacore'
				),
				'gate'         => $flags,
				'block_name'   => $block_name,
				'post_id'      => $postId,
			);
		}

		if (! ( $state['registered'] ?? false )) {
			return array(
				'ready'        => false,
				'missing_step' => 'install_block',
				'message'      => __(
					'The block files are on disk but WordPress has not registered the block yet. Reinstall from the Block Builder library.',
					'lolacore'
				),
				'gate'         => $flags,
				'block_name'   => $block_name,
				'post_id'      => $postId,
			);
		}

		if (! LibraryColumns::isInstalledOnDisk($postId)) {
			return array(
				'ready'        => false,
				'missing_step' => 'install_block',
				'message'      => __(
					'Install this block on the site before placing it in a template or template part.',
					'lolacore'
				),
				'gate'         => $flags,
				'block_name'   => $block_name,
				'post_id'      => $postId,
			);
		}

		return array(
			'ready'        => true,
			'missing_step' => null,
			'message'      => '',
			'gate'         => $flags,
			'block_name'   => $block_name,
			'post_id'      => $postId,
		);
	}

	/**
	 * @throws \Exception When the service or caller is not allowed.
	 */
	private function assertCallable( string $caller ): void {
		if (! self::isAvailable()) {
			throw new \Exception(
				sprintf(
					/* translators: %s: minimum LolaCore version */
					__('Block Builder service requires LolaCore %s or newer.', 'lolacore'),
					self::MIN_LOLACORE_VERSION
				)
			);
		}

		if (! current_user_can('manage_options')) {
			throw new \Exception(__('You do not have permission to manage built blocks.', 'lolacore'));
		}

		/**
		 * Filters addon callers allowed to use BlockBuilderService.
		 *
		 * @param string[] $allowed Caller slugs (e.g. lolacore-for-fse).
		 * @param string   $caller  Caller attempting the operation.
		 */
		$allowed = apply_filters('lolacore_block_builder_service_allowed_callers', array(self::DEFAULT_CALLER), $caller);

		if ($caller === '' || ! in_array($caller, $allowed, true)) {
			throw new \Exception(__('This addon is not allowed to use the Block Builder service.', 'lolacore'));
		}
	}

	/**
	 * @throws \Exception When the post is not a built block.
	 */
	private function assertBlockPost( int $postId ): void {
		if ($postId <= 0) {
			throw new \Exception(__('post_id is required.', 'lolacore'));
		}

		$post = get_post($postId);
		if (! $post instanceof \WP_Post || $post->post_type !== BlockPostType::POST_TYPE) {
			throw new \Exception(__('Built block not found in the library.', 'lolacore'));
		}
	}
}
