<?php
/**
 * Tool for replacing specific blocks using path arrays.
 *
 * @package AgenticWP
 */

namespace Agentic_WP;

use Agentic_WP\Error_Handler;

defined( 'ABSPATH' ) || exit;

/**
 * Tool for replacing specific blocks using path arrays.
 */
class Tool_Replace_Blocks_Segment {
	/**
	 * OpenAI tool schema for Responses API.
	 */
	public static function schema(): array {
		return array(
			'type'        => 'function',
			'name'        => 'replace_blocks_segment',
			'description' => 'Replace specific blocks using path arrays and index ranges',
			'strict'      => true,
			'parameters'  => array(
				'type'                 => 'object',
				'properties'           => array(
					'post_id'     => array(
						'type'        => 'integer',
						'description' => 'Post ID to modify blocks in',
					),
					'path'        => array(
						'type'        => 'array',
						'items'       => array( 'type' => 'integer' ),
						'description' => 'Array of zero-based block indexes for path navigation',
					),
					'start_index' => array(
						'type'        => 'integer',
						'description' => 'Start index for range replacement (inclusive)',
					),
					'end_index'   => array(
						'type'        => 'integer',
						'description' => 'End index for range replacement (inclusive)',
					),
					'blocks'      => array(
						'type'        => 'array',
						'items'       => array( '$ref' => '#/definitions/block' ),
						'description' => 'New blocks to replace the segment',
					),
				),
				'required'             => array( 'post_id', 'path', 'start_index', 'end_index', 'blocks' ),
				'additionalProperties' => false,
				'definitions'          => array(
					'block' => array(
						'type'                 => 'object',
						'properties'           => array(
							'slug'     => array( 'type' => 'string' ),
							'attrs'    => array( 'type' => 'string' ), // JSON-encoded string.
							'text'     => array( 'type' => 'string' ),
							'html'     => array( 'type' => 'string' ),
							'children' => array(
								'type'  => 'array',
								'items' => array( '$ref' => '#/definitions/block' ),
							),
						),
						'required'             => array( 'slug', 'attrs', 'text', 'html', 'children' ),
						'additionalProperties' => false,
					),
				),
			),
		);
	}

	/**
	 * Execute block replacement.
	 *
	 * @param array $args Tool arguments containing post_id, path, start_index, end_index, and blocks.
	 */
	public static function run( array $args ): string {
		$post_id     = (int) ( $args['post_id'] ?? 0 );
		$path        = $args['path'] ?? array();
		$start_index = (int) ( $args['start_index'] ?? 0 );
		$end_index   = (int) ( $args['end_index'] ?? 0 );
		$new_blocks  = $args['blocks'] ?? array();

		if ( ! function_exists( 'get_post' ) || ! function_exists( 'parse_blocks' ) || ! function_exists( 'serialize_blocks' ) ) {
			return 'ERROR: wp_functions_unavailable';
		}

		$post = get_post( $post_id );
		if ( ! $post || 'auto-draft' === $post->post_status ) {
			return 'ERROR: post_not_found';
		}

		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return 'ERROR: insufficient_permissions';
		}

		if ( ! self::validate_path( $path ) ) {
			return 'ERROR: invalid_path';
		}

		if ( $start_index < 0 || $end_index < $start_index || $end_index > 1000 ) {
			return 'ERROR: invalid_range';
		}

		if ( ! is_array( $new_blocks ) ) {
			return 'ERROR: invalid_blocks';
		}

		$current_blocks = parse_blocks( $post->post_content );
		if ( empty( $current_blocks ) ) {
			return 'ERROR: no_blocks_found';
		}

		$wp_blocks = self::convert_structured_blocks( $new_blocks );
		if ( is_wp_error( $wp_blocks ) ) {
			Error_Handler::log_error(
				'tool_replace_blocks',
				$wp_blocks,
				array(
					'post_id' => $post_id,
					'path'    => $path,
				)
			);
			return 'ERROR: block_conversion_failed';
		}

		$updated_blocks = self::replace_segment(
			$current_blocks,
			$path,
			$start_index,
			$end_index,
			$wp_blocks
		);

		if ( is_wp_error( $updated_blocks ) ) {
			Error_Handler::log_error(
				'tool_replace_blocks',
				$updated_blocks,
				array(
					'post_id' => $post_id,
					'path'    => $path,
				)
			);
			return 'ERROR: replacement_failed';
		}

		$new_content = serialize_blocks( $updated_blocks );

		remove_all_actions( 'save_post' );
		remove_all_actions( 'save_post_' . $post->post_type );

		$result = wp_update_post(
			array(
				'ID'           => $post_id,
				'post_content' => $new_content,
			)
		);

		if ( is_wp_error( $result ) || 0 === $result ) {
			if ( is_wp_error( $result ) ) {
				Error_Handler::log_error(
					'tool_replace_blocks',
					$result,
					array( 'post_id' => $post_id )
				);
			}
			return 'ERROR: post_update_failed';
		}

		$json = wp_json_encode(
			array(
				'success'      => true,
				'message'      => 'Blocks replaced successfully',
				'blocks_count' => count( $wp_blocks ),
			)
		);

		if ( false === $json ) {
			return 'ERROR: json_encoding_failed';
		}

		return $json;
	}

	/**
	 * Validates path array for security and reasonableness.
	 *
	 * @since 1.0.0
	 *
	 * @param array $path Array of path segments to validate.
	 * @return bool True if valid, false otherwise.
	 */
	private static function validate_path( array $path ): bool {
		if ( count( $path ) > 10 ) {
			return false;
		}

		foreach ( $path as $segment ) {
			if ( ! is_int( $segment ) || $segment < 0 || $segment > 1000 ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Converts structured blocks to WordPress block format.
	 *
	 * @since 1.0.0
	 *
	 * @param array $structured_blocks Array of structured block data.
	 * @return array|WP_Error Array of WordPress blocks or WP_Error on failure.
	 */
	private static function convert_structured_blocks( array $structured_blocks ) {
		$registry  = \WP_Block_Type_Registry::get_instance();
		$wp_blocks = array();

		foreach ( $structured_blocks as $block_data ) {
			$converted = self::build_block_array( $block_data, $registry );
			if ( is_wp_error( $converted ) ) {
				return $converted; // Return error.
			}
			$wp_blocks[] = $converted;
		}

		return $wp_blocks;
	}

	/**
	 * Builds WordPress block array from structured input.
	 *
	 * @since 1.0.0
	 *
	 * @param array $node     The node data to convert.
	 * @param mixed $registry The block registry instance.
	 * @return array|WP_Error Block array or WP_Error on failure.
	 */
	private static function build_block_array( array $node, $registry ) {
		$slug = $node['slug'] ?? '';

		if ( method_exists( $registry, 'is_registered' ) && ! $registry->is_registered( $slug ) ) {
			Error_Handler::debug_log(
				'Invalid block slug in replace_blocks_segment',
				array( 'slug' => $slug )
			);
			return new \WP_Error( 'invalid_block_slug', 'Unknown block slug: ' . $slug );
		}

		$attrs = array();
		if ( isset( $node['attrs'] ) && is_string( $node['attrs'] ) ) {
			$attrs_string = trim( $node['attrs'] );
			if ( '' === $attrs_string ) {
				$attrs = array();
			} else {
				$decoded = json_decode( $attrs_string, true );
				if ( json_last_error() !== JSON_ERROR_NONE ) {
					Error_Handler::debug_log(
						'Invalid JSON in block attrs',
						array(
							'attrs'      => substr( $node['attrs'], 0, 100 ),
							'json_error' => json_last_error_msg(),
						)
					);
					return new \WP_Error( 'invalid_attrs_json', 'Attrs must be valid JSON' );
				}
				$attrs = $decoded;
			}
		}

		$inner_html = wp_kses_post( $node['html'] ?? '' );

		$children_arr = array();
		if ( isset( $node['children'] ) && is_array( $node['children'] ) ) {
			foreach ( $node['children'] as $child ) {
				$child_block = self::build_block_array( $child, $registry );
				if ( is_wp_error( $child_block ) ) {
					return $child_block;
				}
				$children_arr[] = $child_block;
			}
		}

		return array(
			'blockName'    => $slug,
			'attrs'        => $attrs,
			'innerHTML'    => $inner_html,
			'innerBlocks'  => $children_arr,
			'innerContent' => array( $inner_html ),
		);
	}

	/**
	 * Replaces segment in block hierarchy.
	 *
	 * @since 1.0.0
	 *
	 * @param array $blocks             The blocks array.
	 * @param array $path               The path array for navigation.
	 * @param int   $start_index        Start index for replacement.
	 * @param int   $end_index          End index for replacement.
	 * @param array $replacement_blocks The replacement blocks.
	 * @return array|WP_Error Updated blocks or WP_Error on failure.
	 */
	private static function replace_segment( array $blocks, array $path, int $start_index, int $end_index, array $replacement_blocks ) {
		if ( empty( $path ) ) {
			array_splice( $blocks, $start_index, $end_index - $start_index + 1, $replacement_blocks );
			return $blocks;
		}

		$current = &$blocks;

		$path_count = count( $path );
		for ( $i = 0; $i < $path_count; $i++ ) {
			$index = $path[ $i ];

			if ( ! isset( $current[ $index ] ) ) {
				return new \WP_Error( 'path_not_found', 'Cannot navigate to specified path' );
			}

			if ( $path_count - 1 === $i ) {
				if ( ! isset( $current[ $index ]['innerBlocks'] ) ) {
					$current[ $index ]['innerBlocks'] = array();
				}

				array_splice(
					$current[ $index ]['innerBlocks'],
					$start_index,
					$end_index - $start_index + 1,
					$replacement_blocks
				);
				break;
			}

			if ( ! isset( $current[ $index ]['innerBlocks'] ) || ! is_array( $current[ $index ]['innerBlocks'] ) ) {
				return new \WP_Error( 'invalid_structure', 'Block structure invalid for navigation' );
			}
			$current = &$current[ $index ]['innerBlocks'];
		}

		return $blocks;
	}
}
