<?php

/**
 * Domain handler for Block Builder actions.
 *
 * @package LolaCore\BlockBuilder\Handler
 * @license GPL-2.0-or-later
 */

declare(strict_types=1);

namespace LolaCore\BlockBuilder\Handler;

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

use LolaCore\Actions\ActionLogger;
use LolaCore\Actions\Contracts\ActionHandlerInterface;
use LolaCore\BlockBuilder\Artifact\BlockArtifactAdapter;
use LolaCore\BlockBuilder\Gate\BlockBuilderGate;
use LolaCore\BlockBuilder\Packaging\BlockLocalInstaller;
use LolaCore\BlockBuilder\Playground\BlockBlueprintGenerator;
use LolaCore\BlockBuilder\PostType\BlockPostType;
use LolaCore\BlockBuilder\Scaffold\BlockScaffolder;
use LolaCore\BlockBuilder\Validation\BlockJsonValidator;
use LolaCore\Builder\Artifact\FilesMap;
use LolaCore\Builder\Runtime\RuntimeValidator;
use LolaCore\Builder\Safety\StaticSafetyGate;
use LolaCore\Memory\MemoryManager;

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

/**
 * Executes create, update, list, test, install, and delete flows for built blocks.
 */
class BlockBuilderHandler implements ActionHandlerInterface {

	private const MAX_DETAILS_CODE_CHARS = 90000;

	private BlockScaffolder $scaffolder;
	private BlockJsonValidator $validator;
	private BlockArtifactAdapter $adapter;
	private BlockBlueprintGenerator $blueprintGenerator;
	private BlockLocalInstaller $localInstaller;
	private StaticSafetyGate $safetyGate;
	private RuntimeValidator $runtimeValidator;

	public function __construct(
		?BlockScaffolder $scaffolder = null,
		?BlockJsonValidator $validator = null,
		?BlockArtifactAdapter $adapter = null,
		?BlockBlueprintGenerator $blueprintGenerator = null,
		?BlockLocalInstaller $localInstaller = null,
		?StaticSafetyGate $safetyGate = null,
		?RuntimeValidator $runtimeValidator = null
	) {
		$this->scaffolder          = $scaffolder ?? new BlockScaffolder();
		$this->validator           = $validator ?? new BlockJsonValidator();
		$this->adapter             = $adapter ?? new BlockArtifactAdapter();
		$this->blueprintGenerator  = $blueprintGenerator ?? new BlockBlueprintGenerator();
		$this->localInstaller      = $localInstaller ?? new BlockLocalInstaller();
		$this->safetyGate          = $safetyGate ?? new StaticSafetyGate();
		$this->runtimeValidator    = $runtimeValidator ?? new RuntimeValidator();
	}

	/**
	 * @return array<string>
	 */
	public function getSupportedActions(): array {
		return array(
			'create_block',
			'update_block_code',
			'list_built_blocks',
			'get_built_block',
			'get_installed_block',
			'test_block',
			'install_block',
			'uninstall_block',
			'delete_built_block',
		);
	}

	/**
	 * @param array<string, mixed> $params
	 * @return array<string, mixed>
	 *
	 * @throws \Exception When validation or execution fails.
	 */
	public function handle( string $actionId, array $params, bool $dryRun ): array {
		return match ($actionId) {
			'create_block'       => $this->createBlock($params, $dryRun),
			'update_block_code'  => $this->updateBlockCode($params, $dryRun),
			'list_built_blocks'  => $this->listBlocks($params),
			'get_built_block'    => $this->getBuiltBlock($params),
			'get_installed_block'=> $this->getInstalledBlock($params),
			'test_block'         => $this->testInPlayground($params),
			'install_block'      => $this->installLocal($params, $dryRun),
			'uninstall_block'    => $this->uninstallLocal($params, $dryRun),
			'delete_built_block' => $this->deleteBlock($params, $dryRun),
			default              => throw new \Exception(
				esc_html(
					sprintf(
						/* translators: %s: action identifier */
						__('BlockBuilderHandler: unknown action \'%s\'.', 'lolacore'),
						$actionId
					)
				)
			),
		};
	}

	/**
	 * @param array<string, mixed> $params
	 * @return array<string, mixed>
	 */
	private function createBlock( array $params, bool $dryRun ): array {
		$this->assertManageOptions();

		if (self::callSource($params) !== 'chat') {
			return $this->saveLiteralFiles($params, $dryRun, null);
		}

		$spec = $params['spec'] ?? null;
		if (! is_array($spec)) {
			throw new \Exception(__('spec is required: provide a structured block specification for the scaffolder.', 'lolacore'));
		}

		$normalized = $this->scaffolder->normalizeSpec($spec);
		$block_name = BlockPostType::blockNameFromSlug($normalized['slug']);
		$this->validator->assertBlockNameAvailable($block_name);

		$files = $this->scaffolder->scaffold($spec);

		return $this->persistBlockEntry($normalized, $files, $dryRun, null, 'scaffold');
	}

	/**
	 * @param array<string, mixed> $params
	 * @return array<string, mixed>
	 */
	private function updateBlockCode( array $params, bool $dryRun ): array {
		$this->assertManageOptions();

		$post_id = isset($params['post_id']) ? (int) $params['post_id'] : 0;
		if ($post_id <= 0) {
			throw new \Exception(__('post_id is required.', 'lolacore'));
		}

		$this->getBlockPost($post_id);

		if (self::callSource($params) !== 'chat') {
			return $this->saveLiteralFiles($params, $dryRun, $post_id);
		}

		$spec = $params['spec'] ?? null;
		if (! is_array($spec)) {
			throw new \Exception(__('spec is required: provide the updated block specification.', 'lolacore'));
		}

		$normalized = $this->scaffolder->normalizeSpec($spec);
		$files      = $this->scaffolder->scaffold($spec);

		return $this->persistBlockEntry($normalized, $files, $dryRun, $post_id, 'scaffold');
	}

	/**
	 * @param array<string, mixed> $params
	 * @return array<string, mixed>
	 */
	private function saveLiteralFiles( array $params, bool $dryRun, ?int $postId ): array {
		$raw_files = $params['files'] ?? null;
		if (! is_array($raw_files) || $raw_files === array()) {
			throw new \Exception(__('files is required: provide the literal block file map (block.json, edit.js, …). From an external client Lola stores your exact files — it does not run the scaffolder. Use the wp-admin chat with spec if you want deterministic scaffolding.', 'lolacore'));
		}

		$files = FilesMap::fromArray($this->normalizeFilesParam($raw_files));
		$this->validator->validateFilesMap($files);

		$decoded = json_decode($files->get('block.json'), true);
		if (! is_array($decoded)) {
			throw new \Exception(__('block.json must be valid JSON.', 'lolacore'));
		}

		$block_name = (string) ( $decoded['name'] ?? '' );
		$this->validator->validateBlockName($block_name);

		$block_slug = substr($block_name, strlen(BlockPostType::BLOCK_NAMESPACE . '/'));
		$normalized = array(
			'title'        => (string) ( $decoded['title'] ?? $block_slug ),
			'slug'         => $block_slug,
			'description'  => (string) ( $decoded['description'] ?? '' ),
			'category'     => (string) ( $decoded['category'] ?? BlockScaffolder::DEFAULT_CATEGORY ),
			'icon'         => (string) ( $decoded['icon'] ?? BlockScaffolder::DEFAULT_ICON ),
			'dynamic'      => isset($decoded['render']),
			'inner_blocks' => false,
			'attributes'   => is_array($decoded['attributes'] ?? null) ? $decoded['attributes'] : array(),
			'supports'     => is_array($decoded['supports'] ?? null) ? $decoded['supports'] : array(),
		);

		if (isset($params['name']) && is_string($params['name']) && trim($params['name']) !== '') {
			$normalized['title'] = sanitize_text_field($params['name']);
		}

		if ($postId === null) {
			$this->validator->assertBlockNameAvailable($block_name);
		}

		return $this->persistBlockEntry($normalized, $files, $dryRun, $postId, 'literal');
	}

	/**
	 * @param array<string, mixed> $normalized
	 */
	private function persistBlockEntry(
		array $normalized,
		FilesMap $block_files,
		bool $dryRun,
		?int $postId,
		string $source
	): array {
		$block_slug  = $normalized['slug'];
		$install_slug = BlockPostType::installSlugFromBlockSlug($block_slug);
		$block_name  = BlockPostType::blockNameFromSlug($block_slug);
		$title       = $normalized['title'];
		$description = $normalized['description'];
		$is_update   = $postId !== null && $postId > 0;

		$version = 1;
		if ($is_update) {
			$version = max(1, (int) get_post_meta($postId, BlockPostType::META_VERSION, true)) + 1;
		}

		$analysis = $this->safetyGate->analyzeFiles($block_files, $install_slug, array());
		if (! empty($analysis['blocking'])) {
			throw new \Exception( esc_html( self::formatSafetyBlockMessage($analysis['findings']) ) );
		}

		$review = self::buildReviewFromAnalysis($analysis);

		if ($dryRun) {
			$preview = array(
				'preview'         => true,
				'name'            => $title,
				'block_name'      => $block_name,
				'block_slug'      => $block_slug,
				'plugin_slug'     => $install_slug,
				'install_slug'    => $install_slug,
				'next_version'    => $version,
				'confidence'      => $review['confidence'],
				'confidence_note' => $review['note'],
				'message'         => $is_update
					? __('Will save the block as a new revision in the library.', 'lolacore')
					: __('Will save the block as a new library entry.', 'lolacore'),
				'source'          => $source,
			);
			if ($is_update) {
				$preview['post_id'] = $postId;
			}

			return $preview;
		}

		if ($is_update) {
			$this->persistBlockMeta($postId, $normalized, $block_files, $review, $version);

			$updated = wp_update_post(
				array(
					'ID'         => $postId,
					'post_title' => $title,
				),
				true
			);
			if (is_wp_error($updated)) {
				throw new \Exception(
					esc_html(
						sprintf(
							/* translators: %s: WordPress error message */
							__('Could not update the built block: %s', 'lolacore'),
							$updated->get_error_message()
						)
					)
				);
			}
		} else {
			$new_post_id = wp_insert_post(
				array(
					'post_type'   => BlockPostType::POST_TYPE,
					'post_title'  => $title,
					'post_status' => 'publish',
				),
				true
			);
			if (is_wp_error($new_post_id)) {
				throw new \Exception(
					esc_html(
						sprintf(
							/* translators: %s: WordPress error message */
							__('Could not save the built block: %s', 'lolacore'),
							$new_post_id->get_error_message()
						)
					)
				);
			}
			$postId = (int) $new_post_id;
			$this->persistBlockMeta($postId, $normalized, $block_files, $review, $version);
		}

		BlockBuilderGate::reset($postId);

		$runtime_php = $block_files->has('render.php') ? $block_files->get('render.php') : '';
		$runtime     = $runtime_php !== ''
			? $this->runtimeValidator->validate($runtime_php, $install_slug, array())
			: array('status' => RuntimeValidator::STATUS_SKIPPED);
		update_post_meta($postId, BlockPostType::META_LOAD_SAFE_STATUS, (string) ( $runtime['status'] ?? '' ));

		$logger = new ActionLogger();
		$logger->log(
			$is_update ? 'update_block_code' : 'create_block',
			$is_update
				? sprintf('Block Builder saved "%s" as version %d (post #%d).', $title, $version, $postId)
				: sprintf('Block Builder saved the block "%s" (post #%d).', $title, $postId),
			'success',
			'chat',
			array(
				'post_id'    => $postId,
				'block_name' => $block_name,
				'source'     => $source,
			)
		);

		$memory = new MemoryManager();
		$memory->saveFact(
			'site_state',
			$is_update
				? sprintf("Block Builder saved '%s' (library entry #%d, version %d).", $title, $postId, $version)
				: sprintf("Block Builder saved the block '%s' (library entry #%d).", $title, $postId),
			7,
			'action'
		);

		return array(
			'post_id'         => $postId,
			'name'            => $title,
			'block_name'      => $block_name,
			'block_slug'      => $block_slug,
			'plugin_slug'     => $install_slug,
			'install_slug'    => $install_slug,
			'version'         => $version,
			'confidence'      => $review['confidence'],
			'confidence_note' => $review['note'],
			'suggestions'     => $review['suggestions'],
			'safety_findings' => $analysis['findings'],
			'playground_url'  => null,
			'source'          => $source,
		);
	}

	/**
	 * @param array<string, mixed> $normalized
	 */
	private function persistBlockMeta(
		int $postId,
		array $normalized,
		FilesMap $block_files,
		array $review,
		int $version
	): void {
		$block_slug  = $normalized['slug'];
		$install_slug = BlockPostType::installSlugFromBlockSlug($block_slug);

		update_post_meta($postId, BlockPostType::META_BLOCK_SLUG, $block_slug);
		update_post_meta($postId, BlockPostType::META_NAME, BlockPostType::blockNameFromSlug($block_slug));
		update_post_meta($postId, BlockPostType::META_PLUGIN_SLUG, $install_slug);
		update_post_meta($postId, BlockPostType::META_DESCRIPTION, $normalized['description']);
		update_post_meta($postId, BlockPostType::META_FILES, $block_files->all());
		update_post_meta($postId, BlockPostType::META_VERSION, $version);
		update_post_meta($postId, BlockPostType::META_CONFIDENCE, $review['confidence']);
		update_post_meta($postId, BlockPostType::META_CONFIDENCE_NOTE, $review['note']);
		update_post_meta($postId, BlockPostType::META_SAFETY_FINDINGS, wp_json_encode($review['safety_findings']) ?: '[]');
	}

	/**
	 * @param array<string, mixed> $params
	 * @return array<string, mixed>
	 */
	private function listBlocks( array $params ): array {
		$this->assertManageOptions();

		$limit = isset($params['limit']) ? max(1, min(100, (int) $params['limit'])) : 20;
		$page  = isset($params['page']) ? max(1, (int) $params['page']) : 1;

		$query = new \WP_Query(
			array(
				'post_type'      => BlockPostType::POST_TYPE,
				'post_status'    => array('publish', 'draft', 'private'),
				'posts_per_page' => $limit,
				'paged'          => $page,
				'orderby'        => 'modified',
				'order'          => 'DESC',
			)
		);

		$blocks = array();
		foreach ($query->posts as $post) {
			if (! $post instanceof \WP_Post) {
				continue;
			}

			$blocks[] = array(
				'post_id'     => (int) $post->ID,
				'name'        => $post->post_title,
				'block_name'  => (string) get_post_meta($post->ID, BlockPostType::META_NAME, true),
				'block_slug'  => (string) get_post_meta($post->ID, BlockPostType::META_BLOCK_SLUG, true),
				'plugin_slug' => (string) get_post_meta($post->ID, BlockPostType::META_PLUGIN_SLUG, true),
				'version'     => max(1, (int) get_post_meta($post->ID, BlockPostType::META_VERSION, true)),
				'modified'    => $post->post_modified_gmt,
			);
		}

		return array(
			'blocks' => $blocks,
			'total'  => (int) $query->found_posts,
			'page'   => $page,
			'limit'  => $limit,
		);
	}

	/**
	 * @param array<string, mixed> $params
	 * @return array<string, mixed>
	 */
	private function getBuiltBlock( array $params ): array {
		$this->assertManageOptions();

		$post_id = isset($params['post_id']) ? (int) $params['post_id'] : 0;
		if ($post_id <= 0) {
			throw new \Exception(__('post_id is required.', 'lolacore'));
		}

		$post = $this->getBlockPost($post_id);
		$files = $this->adapter->readBlockFilesMeta($post_id);
		$capped = $this->capFilesForAudit($files);

		return array(
			'post_id'     => $post_id,
			'name'        => $post->post_title,
			'block_name'  => (string) get_post_meta($post_id, BlockPostType::META_NAME, true),
			'version'     => max(1, (int) get_post_meta($post_id, BlockPostType::META_VERSION, true)),
			'description' => (string) get_post_meta($post_id, BlockPostType::META_DESCRIPTION, true),
			'files'       => $capped['files'],
			'audit_warning' => $capped['warning'] !== '' ? $capped['warning'] : null,
		);
	}

	/**
	 * @param array<string, mixed> $params
	 * @return array<string, mixed>
	 */
	private function getInstalledBlock( array $params ): array {
		$this->assertManageOptions();

		$post_id = isset($params['post_id']) ? (int) $params['post_id'] : 0;
		if ($post_id <= 0) {
			throw new \Exception(__('post_id is required.', 'lolacore'));
		}

		$post    = $this->getBlockPost($post_id);
		$version = max(1, (int) get_post_meta($post_id, BlockPostType::META_VERSION, true));
		$disk    = $this->localInstaller->readInstalled($post_id);

		if (! $disk['installed']) {
			return array(
				'post_id'         => $post_id,
				'name'            => $post->post_title,
				'block_name'      => (string) get_post_meta($post_id, BlockPostType::META_NAME, true),
				'installed'       => false,
				'registered'      => false,
				'library_version' => $version,
				'in_sync'         => false,
				'message'         => __('This block is not installed on the site yet. Install it to make it live.', 'lolacore'),
			);
		}

		$expected = $this->adapter->readBlockFilesMeta($post_id);
		$drift    = array();
		foreach ($expected as $path => $content) {
			if (! isset($disk['files'][ $path ]) || $disk['files'][ $path ] !== $content) {
				$drift[] = $path;
			}
		}

		$capped = $this->capFilesForAudit($disk['files']);

		return array(
			'post_id'             => $post_id,
			'name'                => $post->post_title,
			'block_name'          => (string) get_post_meta($post_id, BlockPostType::META_NAME, true),
			'installed'           => true,
			'registered'          => $disk['registered'],
			'install_slug'        => $disk['install_slug'],
			'install_path'        => $disk['install_path'],
			'version_header'      => $disk['version_header'],
			'library_version'     => $version,
			'in_sync'             => $drift === array(),
			'drift'               => $drift,
			'files'               => $capped['files'],
			'audit_warning'       => $capped['warning'] !== '' ? $capped['warning'] : null,
		);
	}

	/**
	 * @param array<string, mixed> $params
	 * @return array<string, mixed>
	 */
	private function testInPlayground( array $params ): array {
		$this->assertManageOptions();

		$post_id = isset($params['post_id']) ? (int) $params['post_id'] : 0;
		if ($post_id <= 0) {
			throw new \Exception(__('post_id is required.', 'lolacore'));
		}

		$post = $this->getBlockPost($post_id);
		$url  = $this->blueprintGenerator->build($post_id);

		return array(
			'post_id'        => $post_id,
			'name'           => $post->post_title,
			'block_name'     => (string) get_post_meta($post_id, BlockPostType::META_NAME, true),
			'version'        => max(1, (int) get_post_meta($post_id, BlockPostType::META_VERSION, true)),
			'playground_url' => $url,
		);
	}

	/**
	 * @param array<string, mixed> $params
	 * @return array<string, mixed>
	 */
	private function installLocal( array $params, bool $dryRun ): array {
		$this->assertManageOptions();

		$post_id = isset($params['post_id']) ? (int) $params['post_id'] : 0;
		if ($post_id <= 0) {
			throw new \Exception(__('post_id is required.', 'lolacore'));
		}

		$post = $this->getBlockPost($post_id);
		$already_installed = $this->localInstaller->isAlreadyInstalled($post_id);

		BlockBuilderGate::assertInstallAllowed($post_id);

		if ($dryRun) {
			return array(
				'preview'           => true,
				'post_id'           => $post_id,
				'name'              => $post->post_title,
				'block_name'        => (string) get_post_meta($post_id, BlockPostType::META_NAME, true),
				'already_installed' => $already_installed,
				'message'           => $already_installed
					? __('A version is already installed. This will overwrite the existing block files on this site.', 'lolacore')
					: __('Will install this built block on this site. It will appear in the block inserter.', 'lolacore'),
			);
		}

		BlockBuilderGate::assertLoadSafe($post_id, $this->runtimeValidator);

		$overwrite = ! empty($params['overwrite']) || $already_installed;
		$result    = $this->localInstaller->install($post_id, $overwrite);

		$logger = new ActionLogger();
		$logger->log(
			'install_block',
			sprintf('Block Builder installed "%s" on this site (%s).', $post->post_title, $result['install_path']),
			'success',
			'chat',
			array(
				'post_id'      => $post_id,
				'install_slug' => $result['install_slug'],
			)
		);

		return $result;
	}

	/**
	 * @param array<string, mixed> $params
	 * @return array<string, mixed>
	 */
	private function uninstallLocal( array $params, bool $dryRun ): array {
		$this->assertManageOptions();

		$post_id = isset($params['post_id']) ? (int) $params['post_id'] : 0;
		if ($post_id <= 0) {
			throw new \Exception(__('post_id is required.', 'lolacore'));
		}

		$post = $this->getBlockPost($post_id);
		$installed = $this->localInstaller->isAlreadyInstalled($post_id);

		if ($dryRun) {
			return array(
				'preview'   => true,
				'post_id'   => $post_id,
				'name'      => $post->post_title,
				'installed' => $installed,
				'message'   => $installed
					? __('Will remove this block from wp-content/lolacore-blocks on this site and unregister it from the block inserter. The library entry stays in the catalog.', 'lolacore')
					: __('This block is not installed on the site — nothing to remove from disk.', 'lolacore'),
			);
		}

		if (! $installed) {
			throw new \Exception(__('This block is not installed on the site yet.', 'lolacore'));
		}

		$result = $this->localInstaller->uninstall($post_id);

		$logger = new ActionLogger();
		$logger->log(
			'uninstall_block',
			sprintf('Block Builder uninstalled "%s" from this site (%s).', $post->post_title, $result['install_path']),
			'success',
			'chat',
			array(
				'post_id'      => $post_id,
				'install_slug' => $result['install_slug'],
			)
		);

		return $result;
	}

	/**
	 * @param array<string, mixed> $params
	 * @return array<string, mixed>
	 */
	private function deleteBlock( array $params, bool $dryRun ): array {
		$this->assertManageOptions();

		$post_id = isset($params['post_id']) ? (int) $params['post_id'] : 0;
		if ($post_id <= 0) {
			throw new \Exception(__('post_id is required.', 'lolacore'));
		}

		$post = $this->getBlockPost($post_id);

		if ($dryRun) {
			return array(
				'preview' => true,
				'action'  => __('Will move built block to trash (library only — installed copy on disk is not removed)', 'lolacore'),
				'post_id' => $post_id,
				'name'    => $post->post_title,
			);
		}

		$result = wp_trash_post($post_id);
		if ($result === false || $result === null) {
			throw new \Exception(__('Could not move the built block to trash.', 'lolacore'));
		}

		return array(
			'post_id' => $post_id,
			'name'    => $post->post_title,
			'status'  => 'trashed',
		);
	}

	private function getBlockPost( int $post_id ): \WP_Post {
		$post = get_post($post_id);
		if (! $post instanceof \WP_Post || $post->post_type !== BlockPostType::POST_TYPE) {
			throw new \Exception(__('Built block not found.', 'lolacore'));
		}

		return $post;
	}

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

	/**
	 * @param array<string, mixed> $params
	 */
	private static function callSource( array $params ): string {
		$source = isset($params['__lc_source']) ? (string) $params['__lc_source'] : 'chat';

		return $source !== '' ? $source : 'chat';
	}

	/**
	 * @param array<mixed, mixed> $raw
	 * @return array<string, string>
	 */
	private function normalizeFilesParam( array $raw ): array {
		$files = array();
		foreach ($raw as $path => $content) {
			if (is_string($path) && is_string($content)) {
				$files[ $path ] = $content;
			}
		}

		return $files;
	}

	/**
	 * @param array<int, array<string, mixed>> $findings
	 */
	private static function formatSafetyBlockMessage( array $findings ): string {
		$lines = array();
		foreach ($findings as $finding) {
			$severity = (string) ( $finding['severity'] ?? '' );
			if ($severity !== 'fatal' && $severity !== 'error') {
				continue;
			}
			$lines[] = sprintf(
				'- [%s] %s: %s',
				$severity,
				(string) ( $finding['code'] ?? 'unknown' ),
				(string) ( $finding['message'] ?? '' )
			);
		}

		return __('I couldn\'t save this block safely. Fix these issues and try again:', 'lolacore') . "\n" . implode("\n", $lines);
	}

	/**
	 * @param array{blocking: bool, confidence_cap: ?int, findings: array<int, array<string, mixed>>} $analysis
	 * @return array{confidence: int, note: string, suggestions: array<int, string>, safety_findings: array<int, array<string, mixed>>}
	 */
	private static function buildReviewFromAnalysis( array $analysis ): array {
		$cap        = $analysis['confidence_cap'] ?? null;
		$confidence = is_int($cap) ? $cap : 100;
		$note       = $confidence >= 100
			? __('Block passed deterministic static safety checks.', 'lolacore')
			: __('Block saved with non-blocking safety warnings.', 'lolacore');

		$suggestions = array();
		foreach ($analysis['findings'] as $finding) {
			$message = isset($finding['message']) ? trim((string) $finding['message']) : '';
			if ($message !== '') {
				$suggestions[] = $message;
			}
			if (count($suggestions) >= 5) {
				break;
			}
		}

		return array(
			'confidence'      => $confidence,
			'note'            => $note,
			'suggestions'     => $suggestions,
			'safety_findings' => $analysis['findings'],
		);
	}

	/**
	 * @param array<string, string> $files
	 * @return array{files: array<string, string>, warning: string}
	 */
	private function capFilesForAudit( array $files ): array {
		$total = 0;
		foreach ($files as $content) {
			$total += mb_strlen($content);
		}

		if ($total <= self::MAX_DETAILS_CODE_CHARS) {
			return array('files' => $files, 'warning' => '');
		}

		return array(
			'files'   => array('block.json' => mb_substr($files['block.json'] ?? '', 0, 4000)),
			'warning' => __('File map exceeds the inline audit budget; partial content returned.', 'lolacore'),
		);
	}

	public static function slimToolResultForHistory( string $actionId, array $result ): string {
		$name = (string) ( $result['name'] ?? 'block' );

		if ($actionId === 'test_block') {
			$version = max(1, (int) ( $result['version'] ?? 1 ));

			return sprintf(
				/* translators: 1: block name, 2: version number */
				__('Playground URL generated for %1$s (v%2$d).', 'lolacore'),
				$name,
				$version
			);
		}

		if ($actionId === 'create_block' || $actionId === 'update_block_code') {
			$version = max(1, (int) ( $result['version'] ?? 1 ));

			return sprintf(
				/* translators: 1: block name, 2: version number */
				__('Block "%1$s" saved (v%2$d).', 'lolacore'),
				$name,
				$version
			);
		}

		return (string) wp_json_encode($result);
	}

	public static function artifactIntroMessage( string $artifactType ): string {
		return match ($artifactType) {
			'block_playground' => __('Your block is ready to test in Playground.', 'lolacore'),
			'block_created'    => __('Your block is saved to the library.', 'lolacore'),
			'block_updated'    => __('Your block has been updated.', 'lolacore'),
			default            => __('Action completed.', 'lolacore'),
		};
	}

	/**
	 * @param array<string, mixed> $result
	 * @return array<string, mixed>
	 */
	public static function artifactDataForUi( string $artifactType, array $result ): array {
		$keys = array('post_id', 'name', 'block_name', 'block_slug', 'plugin_slug', 'version', 'confidence', 'confidence_note');
		$data = array();
		foreach ($keys as $key) {
			if (array_key_exists($key, $result)) {
				$data[ $key ] = $result[ $key ];
			}
		}

		if (! empty($data['block_slug']) && ! isset($data['slug'])) {
			$data['slug'] = $data['block_slug'];
		}

		if ($artifactType === 'block_playground' && ! empty($result['playground_url'])) {
			$data['playground_url'] = (string) $result['playground_url'];
		}

		if (! empty($data['post_id'])) {
			$data = array_merge($data, BlockBuilderGate::uiFlags((int) $data['post_id']));
		}

		return $data;
	}
}


