<?php



/**

 * Installs built blocks into wp-content/lolacore-blocks and registers them.

 *

 * @package LolaCore\BlockBuilder\Packaging

 * @license GPL-2.0-or-later

 */



declare(strict_types=1);



namespace LolaCore\BlockBuilder\Packaging;

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



use LolaCore\BlockBuilder\Artifact\BlockArtifactAdapter;

use LolaCore\BlockBuilder\PostType\BlockPostType;

use LolaCore\BlockBuilder\Registry\BlockRegistry;

use LolaCore\Builder\Packaging\TreeWriteException;



if (! defined('ABSPATH')) {

	exit;

}



/**

 * Writes block file trees to lolacore-blocks/ and registers via BlockRegistry.

 */

class BlockLocalInstaller {



	private BlockArtifactAdapter $adapter;

	private BlockTreeInstaller $treeInstaller;

	private BlockRegistry $registry;



	public function __construct(

		?BlockArtifactAdapter $adapter = null,

		?BlockTreeInstaller $treeInstaller = null,

		?BlockRegistry $registry = null

	) {

		$this->adapter       = $adapter ?? new BlockArtifactAdapter();

		$this->treeInstaller = $treeInstaller ?? new BlockTreeInstaller();

		$this->registry      = $registry ?? new BlockRegistry();

	}



	public function isAlreadyInstalled( int $postId ): bool {

		$slug = $this->adapter->slug($postId);

		if ($slug === '') {

			return false;

		}



		return $this->treeInstaller->isInstalled($slug);

	}



	/**

	 * @return array{

	 *     status: string,

	 *     post_id: int,

	 *     name: string,

	 *     install_slug: string,

	 *     plugin_slug: string,

	 *     block_name: string,

	 *     install_path: string,

	 *     registered: bool,

	 *     overwritten: bool,

	 *     message: string

	 * }

	 */

	public function install( int $postId, bool $overwrite = false ): array {

		if (is_multisite()) {

			throw new \Exception(__('Direct installation is not available on multisite.', 'lolacore'));

		}



		$post = get_post($postId);

		if (! $post instanceof \WP_Post) {

			throw new \Exception(__('Built block not found.', 'lolacore'));

		}



		$install_slug = $this->adapter->slug($postId);

		if ($install_slug === '') {

			throw new \Exception(__('Built block install slug is missing.', 'lolacore'));

		}



		$overwritten = $this->isAlreadyInstalled($postId);

		if ($overwritten && ! $overwrite) {

			throw new \Exception(__('A version is already installed. Overwrite it?', 'lolacore'));

		}



		try {

			$this->treeInstaller->writeTree($install_slug, $this->adapter->filesMap($postId));

		} catch (TreeWriteException $e) {

			throw new \Exception( esc_html( $this->treeFailureMessage($e->relPath()) ) );

		}



		$this->registry->registerInstallSlug($install_slug);



		$block_name = (string) get_post_meta($postId, BlockPostType::META_NAME, true);

		$registered = $this->registry->isBlockRegistered($block_name);

		$path       = BlockPostType::runtimePathForSlug($install_slug);



		return array(

			'status'        => 'installed',

			'post_id'       => $postId,

			'name'          => $post->post_title,

			'install_slug'  => $install_slug,

			'plugin_slug'   => $install_slug,

			'block_name'    => $block_name,

			'install_path'  => $path,

			'registered'    => $registered,

			'overwritten'   => $overwritten,

			'message'       => $overwritten

				? __('I updated the block on this site. It is available in the block inserter.', 'lolacore')

				: __('I installed the block on this site. It is available in the block inserter.', 'lolacore'),

		);

	}



	/**

	 * @return array{

	 *     installed: bool,

	 *     registered: bool,

	 *     install_slug: string,

	 *     install_path: string,

	 *     version_header: string,

	 *     files: array<string, string>

	 * }

	 */

	public function readInstalled( int $postId ): array {

		$install_slug = $this->adapter->slug($postId);

		if ($install_slug === '') {

			throw new \Exception(__('Built block install slug is missing.', 'lolacore'));

		}



		$directory = BlockPostType::runtimePathForSlug($install_slug);

		$installed = $this->treeInstaller->isInstalled($install_slug);



		if (! $installed) {

			return array(

				'installed'      => false,

				'registered'     => false,

				'install_slug'   => $install_slug,

				'install_path'   => $directory,

				'version_header' => '',

				'files'          => array(),

			);

		}



		$files = array();

		foreach (array('block.json', 'edit.js', 'edit.asset.php', 'style.css', 'render.php') as $rel) {

			$path = $directory . '/' . $rel;

			if (file_exists($path)) {

				$contents = file_get_contents($path);

				$files[ $rel ] = is_string($contents) ? $contents : '';

			}

		}



		$block_name = (string) get_post_meta($postId, BlockPostType::META_NAME, true);

		$version    = self::parseVersionFromBlockJson($files['block.json'] ?? '');



		return array(

			'installed'      => true,

			'registered'     => $this->registry->isBlockRegistered($block_name),

			'install_slug'   => $install_slug,

			'install_path'   => $directory,

			'version_header' => $version,

			'files'          => $files,

		);

	}



	/**

	 * @return array{

	 *     status: string,

	 *     post_id: int,

	 *     name: string,

	 *     install_slug: string,

	 *     block_name: string,

	 *     install_path: string,

	 *     registered: bool,

	 *     message: string

	 * }

	 */

	public function uninstall( int $postId ): array {

		if (is_multisite()) {

			throw new \Exception(__('Direct uninstall is not available on multisite.', 'lolacore'));

		}



		$post = get_post($postId);

		if (! $post instanceof \WP_Post) {

			throw new \Exception(__('Built block not found.', 'lolacore'));

		}



		$install_slug = $this->adapter->slug($postId);

		if ($install_slug === '') {

			throw new \Exception(__('Built block install slug is missing.', 'lolacore'));

		}



		if (! $this->treeInstaller->isInstalled($install_slug)) {

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

		}



		$block_name = (string) get_post_meta($postId, BlockPostType::META_NAME, true);

		$path       = BlockPostType::runtimePathForSlug($install_slug);



		$this->registry->unregisterInstallSlug($install_slug);



		if (! $this->treeInstaller->removeTree($install_slug)) {

			throw new \Exception(__('Could not remove the installed block files from this site.', 'lolacore'));

		}



		return array(

			'status'       => 'uninstalled',

			'post_id'      => $postId,

			'name'         => $post->post_title,

			'install_slug' => $install_slug,

			'block_name'   => $block_name,

			'install_path' => $path,

			'registered'   => $this->registry->isBlockRegistered($block_name),

			'message'      => __('I removed the block from this site. It is no longer in the block inserter.', 'lolacore'),

		);

	}



	public static function parseVersionFromBlockJson( string $json ): string {

		if ($json === '') {

			return '';

		}



		$decoded = json_decode($json, true);

		if (! is_array($decoded)) {

			return '';

		}



		$version = isset($decoded['version']) ? (string) $decoded['version'] : '';



		return trim($version);

	}



	private function readFileIfExists( string $path ): string {

		if (! file_exists($path)) {

			return '';

		}



		$contents = file_get_contents($path);



		return is_string($contents) ? $contents : '';

	}



	private function treeFailureMessage( string $relPath ): string {

		return match (strtolower(pathinfo($relPath, PATHINFO_EXTENSION))) {

			'php'   => __('Could not write the block PHP file.', 'lolacore'),

			'js'    => __('Could not write the block JavaScript file.', 'lolacore'),

			'css'   => __('Could not write the block CSS file.', 'lolacore'),

			'json'  => __('Could not write block.json.', 'lolacore'),

			default => __('Could not create the block directory.', 'lolacore'),

		};

	}

}



