<?php

/**
 * Writes built-artifact file trees into wp-content/plugins on the current site.
 *
 * @package LolaCore\Builder\Packaging
 * @license GPL-2.0-or-later
 */

declare(strict_types=1);

namespace LolaCore\Builder\Packaging;

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

use LolaCore\Builder\Artifact\FilesMap;

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

/**
 * Filesystem mechanics shared by every artifact installer: syntax-checks PHP,
 * deactivates the artifact's main file if active, creates directories, and
 * writes the validated tree. Activation is always a separate, explicit step
 * owned by the consumer.
 */
final class TreeInstaller {

	/**
	 * Whether the artifact directory or main file already exists on disk.
	 */
	public function isInstalled( string $slug ): bool {
		$directory = WP_PLUGIN_DIR . '/' . $slug;

		return is_dir($directory) || file_exists($directory . '/' . $slug . '.php');
	}

	/**
	 * Write the artifact tree under wp-content/plugins/{slug}/ without activating.
	 *
	 * @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 $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);
		}

		$mainFile = $slug . '/' . $slug . '.php';
		if (is_plugin_active($mainFile)) {
			deactivate_plugins($mainFile, true);
		}

		$root = WP_PLUGIN_DIR . '/' . $slug;
		$this->ensureDirectory($wp_filesystem, $root, '');

		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
					)
				);
			}
		}
	}

	/**
	 * Parse-check PHP without activating the artifact or using the theme/plugin editor loopback.
	 *
	 * @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 ) );
		}
	}

	/**
	 * Create every directory segment of $relDir under $root.
	 *
	 * WP_Filesystem's mkdir is not recursive on every transport, so segments
	 * are created one by one.
	 */
	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';
		}
		if (! function_exists('activate_plugin')) {
			require_once ABSPATH . 'wp-admin/includes/plugin.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'));
		}
	}
}

