<?php

/**
 * Writes built-block file trees into wp-content/lolacore-blocks.
 *
 * @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\PostType\BlockPostType;
use LolaCore\Builder\Artifact\FilesMap;
use LolaCore\Builder\Packaging\TreeWriteException;

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

/**
 * Filesystem writer for block runtime trees (no plugin activation).
 */
final class BlockTreeInstaller {

	/**
	 * Whether the block directory and block.json already exist on disk.
	 */
	public function isInstalled( string $install_slug ): bool {
		$directory = BlockPostType::runtimePathForSlug($install_slug);

		return is_dir($directory) && file_exists($directory . '/block.json');
	}

	/**
	 * Write the block tree under wp-content/lolacore-blocks/{slug}/.
	 *
	 * @throws \Exception When PHP is invalid or the filesystem is unavailable.
	 * @throws TreeWriteException When a directory or file cannot be written.
	 */
	public function writeTree( string $install_slug, FilesMap $files ): void {
		$this->loadAdminDependencies();
		$this->initFilesystem();

		global $wp_filesystem;

		if (! $wp_filesystem instanceof \WP_Filesystem_Base) {
			throw new \Exception(__('Could not initialize the WordPress filesystem.', 'lolacore'));
		}

		foreach ($files->phpFiles() as $php) {
			$this->validatePhpSyntax($php);
		}

		$root = BlockPostType::runtimePathForSlug($install_slug);
		$this->ensureDirectory($wp_filesystem, BlockPostType::runtimeBlocksRoot(), '');
		$this->ensureDirectory($wp_filesystem, $root, $install_slug);

		foreach ($files->all() as $relPath => $content) {
			$relDir = dirname($relPath);
			if ($relDir !== '.' && $relDir !== '') {
				$this->ensureDirectoryPath($wp_filesystem, $root, $relDir);
			}

			if (! $wp_filesystem->put_contents($root . '/' . $relPath, $content, FS_CHMOD_FILE)) {
				throw new TreeWriteException(
					$relPath,
					sprintf(
						/* translators: %s: relative file path */
						__('Could not write %s.', 'lolacore'),
						$relPath
					)
				);
			}
		}
	}

	/**
	 * Remove an installed block tree from wp-content/lolacore-blocks/{slug}/.
	 */
	public function removeTree( string $install_slug ): bool {
		$this->loadAdminDependencies();
		$this->initFilesystem();

		global $wp_filesystem;

		if (! $wp_filesystem instanceof \WP_Filesystem_Base) {
			throw new \Exception(__('Could not initialize the WordPress filesystem.', 'lolacore'));
		}

		$root = BlockPostType::runtimePathForSlug($install_slug);
		if ($root === '' || ! $wp_filesystem->is_dir($root)) {
			return false;
		}

		return (bool) $wp_filesystem->rmdir($root, true);
	}

	/**
	 * @throws \Exception When the generated PHP is not valid.
	 */
	private function validatePhpSyntax( string $php ): void {
		if (! function_exists('token_get_all')) {
			return;
		}

		try {
			// phpcs:ignore PHPCompatibility.FunctionUse.NewFunctionParameters.token_get_all_flagsFound -- TOKEN_PARSE requires PHP 7.4+.
			token_get_all($php, TOKEN_PARSE);
		} catch (\ParseError $e) {
			$message = __('PHP syntax validation failed.', 'lolacore');
			$line    = $e->getLine();
			if ($line > 0) {
				$message .= sprintf(
					/* translators: %d: line number in PHP source */
					__(' Error near line %d.', 'lolacore'),
					$line
				);
			}
			throw new \Exception( esc_html( $message ) );
		}
	}

	private function ensureDirectoryPath( \WP_Filesystem_Base $wp_filesystem, string $root, string $relDir ): void {
		$current = $root;
		foreach (explode('/', $relDir) as $segment) {
			$current .= '/' . $segment;
			$this->ensureDirectory($wp_filesystem, $current, $relDir);
		}
	}

	private function ensureDirectory( \WP_Filesystem_Base $wp_filesystem, string $path, string $relPath ): void {
		if ($wp_filesystem->is_dir($path)) {
			return;
		}

		if (! $wp_filesystem->mkdir($path, FS_CHMOD_DIR)) {
			throw new TreeWriteException(
				$relPath,
				sprintf(
					/* translators: %s: directory path */
					__('Could not create directory %s.', 'lolacore'),
					$path
				)
			);
		}
	}

	private function loadAdminDependencies(): void {
		if (! function_exists('WP_Filesystem')) {
			require_once ABSPATH . 'wp-admin/includes/file.php';
		}
	}

	private function initFilesystem(): void {
		global $wp_filesystem;

		if ($wp_filesystem instanceof \WP_Filesystem_Base) {
			return;
		}

		if (! WP_Filesystem()) {
			throw new \Exception(__('Could not initialize the WordPress filesystem.', 'lolacore'));
		}
	}
}

