<?php
/**
 * Tool registry. Tools register a name + JSON schema + handler. The MCP
 * server queries the registry for tools/list and tools/call.
 */

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

class PCD_PC_Tools {

	private static $tools = [];

	/**
	 * Register a tool.
	 *
	 * @param string   $name        Tool name (snake_case).
	 * @param string   $description One-line description for the AI.
	 * @param array    $schema      JSON schema for input parameters.
	 * @param callable $handler     callable( array $args ): array|WP_Error
	 * @param array    $opts        {
	 *     @type string $cap          Required WP capability. Default 'manage_options'.
	 *     @type bool   $destructive  If true, requires confirm:true in args (or returns dry-run).
	 *     @type string $category     Category label for docs.
	 * }
	 */
	public static function register( string $name, string $description, array $schema, callable $handler, array $opts = [] ): void {
		// WordPress.org (Lite) build: never expose tools that edit/install code,
		// manage plugins/themes/users, fetch remote files, or self-update. Those
		// capabilities live only in the full relay/marketplace build.
		if ( function_exists( 'pcd_pc_is_wporg_build' ) && pcd_pc_is_wporg_build() ) {
			static $wporg_denylist = [
				'wp_file_read', 'wp_file_write', 'wp_file_edit', 'wp_file_list', 'wp_file_outline',
				'wp_plugin_install', 'wp_plugin_install_zip', 'wp_plugin_activate', 'wp_plugin_deactivate', 'wp_plugin_update', 'wp_plugin_delete',
				'wp_theme_activate', 'wp_theme_delete',
				'wp_user_create', 'wp_user_update', 'wp_user_delete',
				'wp_search_replace', 'pcd_pc_fetch_url', 'pcd_pc_self_update', 'pcd_pc_check_update',
			];
			if ( in_array( $name, $wporg_denylist, true ) ) {
				return;
			}
		}
		self::$tools[ $name ] = [
			'name'        => $name,
			'description' => $description,
			'input_schema' => $schema,
			'handler'     => $handler,
			'cap'         => $opts['cap'] ?? 'manage_options',
			'destructive' => ! empty( $opts['destructive'] ),
			'category'    => $opts['category'] ?? 'general',
		];
	}

	/**
	 * Return all tools in MCP wire format (without handler / cap metadata).
	 */
	public static function list_for_mcp(): array {
		$out = [];
		foreach ( self::$tools as $tool ) {
			$out[] = [
				'name'        => $tool['name'],
				'description' => $tool['description'],
				'inputSchema' => $tool['input_schema'],
			];
		}
		return $out;
	}

	public static function get( string $name ): ?array {
		return self::$tools[ $name ] ?? null;
	}

	public static function call( string $name, array $args ) {
		$tool = self::get( $name );
		if ( ! $tool ) {
			return new WP_Error( 'unknown_tool', "Tool not found: {$name}", [ 'status' => 404 ] );
		}

		if ( ! self::user_can_run( $tool ) ) {
			return new WP_Error( 'forbidden', self::forbidden_message( $tool ), [ 'status' => 403 ] );
		}

		// Destructive ops require explicit confirm:true.
		if ( $tool['destructive'] && empty( $args['confirm'] ) ) {
			return [
				'requires_confirmation' => true,
				'tool'    => $name,
				'message' => 'Destructive operation. Re-call with confirm: true to proceed.',
				'preview' => $args,
			];
		}

		$start  = microtime( true );
		$result = call_user_func( $tool['handler'], $args );
		$ms     = (int) ( ( microtime( true ) - $start ) * 1000 );

		PCD_PC_Audit::record( $name, $args, $result, $ms );

		// Record a live-reload change entry for write-y tool calls. Reads/lookups
		// are skipped — only inform the customer when state actually mutated.
		if ( class_exists( 'PCD_PC_Change_Tracker' ) && ! is_wp_error( $result ) ) {
			self::maybe_track_change( $name, $args, $result );
		}

		return $result;
	}

	/**
	 * Authorize a tool call. Normally just the declared capability — but file
	 * tools also honor an explicit owner opt-in (the "Allow file management"
	 * setting), so they work on the linked administrator even when edit_themes
	 * is unavailable (e.g. DISALLOW_FILE_EDIT strips it from everyone).
	 */
	private static function user_can_run( array $tool ): bool {
		if ( current_user_can( $tool['cap'] ) ) {
			return true;
		}
		if ( ( $tool['category'] ?? '' ) === 'files'
			&& current_user_can( 'manage_options' )
			&& get_option( 'pcd_pc_allow_file_tools' ) ) {
			return true;
		}
		return false;
	}

	/** Actionable 403 message — names the real cause for blocked file tools. */
	private static function forbidden_message( array $tool ): string {
		if ( ( $tool['category'] ?? '' ) === 'files' ) {
			if ( defined( 'DISALLOW_FILE_EDIT' ) && DISALLOW_FILE_EDIT ) {
				return 'File tools are blocked: this site has DISALLOW_FILE_EDIT set in wp-config.php, which removes the edit_themes capability. Enable "Allow file management" in PressConduit → Settings to let PressConduit manage files anyway (runs as the linked administrator).';
			}
			return 'File tools require the edit_themes capability, or enable "Allow file management" in PressConduit → Settings. The linked user has neither.';
		}
		return "Missing capability: {$tool['cap']}";
	}

	/**
	 * Translate a tool result into a human-readable "something changed" entry
	 * for the live-reload toast. Skips read-only tools by name pattern.
	 */
	private static function maybe_track_change( string $name, array $args, $result ): void {
		// Skip read-only tools — they don't move the needle for the watcher.
		$read_patterns = [
			'_list', '_get', '_read', '_docs', '_status', '_check_update', 'pcd_snapshot_list', 'wp_site_info',
			'wp_taxonomy_list',
		];
		foreach ( $read_patterns as $p ) {
			if ( false !== strpos( $name, $p ) ) {
				return;
			}
		}

		$result_arr = is_array( $result ) ? $result : [];
		$post_id    = (int) ( $result_arr['id'] ?? $result_arr['post_id'] ?? 0 );

		// Build a sensible summary, action label, and URL based on tool name.
		$summary      = $name;
		$action_label = 'Refresh';
		$url          = '';
		$admin_url    = '';

		if ( strpos( $name, 'bricks_create_page' ) === 0 || strpos( $name, 'bricks_create_post' ) === 0 ) {
			$summary      = 'New ' . ( isset( $result_arr['type'] ) ? $result_arr['type'] : 'post' ) . ' created';
			$action_label = 'Open in Builder';
			$url          = $result_arr['preview_url'] ?? '';
			$admin_url    = $result_arr['builder_url'] ?? $result_arr['edit_url'] ?? '';
		} elseif ( strpos( $name, 'wp_post_create' ) === 0 ) {
			$summary      = 'Post created';
			$action_label = 'Edit';
			$url          = $result_arr['permalink'] ?? '';
			$admin_url    = $result_arr['edit_url'] ?? '';
		} elseif ( strpos( $name, 'wp_post_update' ) === 0 ) {
			$summary      = 'Post updated';
			$action_label = 'View';
			$url          = $result_arr['permalink'] ?? '';
		} elseif ( strpos( $name, 'wp_post_delete' ) === 0 ) {
			$summary      = 'Post trashed';
		} elseif ( strpos( $name, 'wp_media_upload' ) === 0 ) {
			$summary      = 'Media uploaded: ' . ( $result_arr['title'] ?? 'image' );
			$action_label = 'View';
			$url          = $result_arr['url'] ?? '';
		} elseif ( strpos( $name, 'bricks_template_create' ) === 0 ) {
			$summary      = 'Bricks template created';
			$action_label = 'Open Builder';
			$admin_url    = $result_arr['edit_url'] ?? admin_url( 'edit.php?post_type=bricks_template' );
		} elseif ( strpos( $name, 'bricks_set_post_content' ) === 0 ) {
			$summary      = 'Bricks content applied to post';
			$action_label = 'View';
			$url          = get_permalink( $post_id ) ?: '';
		} elseif ( strpos( $name, 'wp_plugin_activate' ) === 0 || strpos( $name, 'wp_plugin_deactivate' ) === 0 || strpos( $name, 'wp_plugin_install' ) === 0 ) {
			$summary = $name;
		} elseif ( strpos( $name, 'wp_theme_activate' ) === 0 ) {
			$summary      = 'Theme switched';
			$action_label = 'View Site';
			$url          = home_url( '/' );
		} else {
			// Generic write — just notify with the tool name.
			$summary = ucfirst( str_replace( '_', ' ', $name ) );
		}

		PCD_PC_Change_Tracker::mark( [
			'summary'      => $summary,
			'action_label' => $action_label,
			'url'          => $url,
			'admin_url'    => $admin_url,
			'post_id'      => $post_id,
		] );
	}
}
