<?php
/**
 * ACFBlockMigration
 *
 * @package StrategyBlocks
 */

namespace StrategyBlocks;

use StrategyBlocks\Utility;
use StrategyBlocks\FontAwesomeSVG;

use WP_CLI;

/**
 * Creates the StrategyBlocks post type and shortcodes to output them.
 */
class ACFBlockMigration extends \StrategyBlocks\Module {

	/**
	 * The store of the output of the migration commands.
	 *
	 * @var array<string>
	 */
	private $output = [];

	/**
	 * Map of legacy ACF block names to their equivalent native Strategy Blocks names.
	 *
	 * Keys are the original ACF block identifiers (e.g. `acf/accordion`),
	 * and values are the new block identifiers under the `leadgen` namespace
	 * (e.g. `leadgen/accordion`).
	 *
	 * This map is used during migration to rename blocks from their ACF
	 * implementation to their native Strategy Blocks implementation.
	 *
	 * @var array<string,string>
	 */
	private $block_map = [
		'acf/accordion' => 'leadgen/accordion',
		'acf/text-editor' => 'leadgen/text-editor',
		'acf/heading' => 'leadgen/heading',
		'acf/shortcode' => 'leadgen/shortcode',
		'acf/button' => 'leadgen/button',
		'acf/divider' => 'leadgen/divider',
		'acf/copyright' => 'leadgen/copyright',
		'acf/spacer' => 'leadgen/spacer',
		'acf/media' => 'leadgen/media',
		'acf/site-logo' => 'leadgen/site-logo',
		'acf/icon' => 'leadgen/icon',
		'acf/icon-list' => 'leadgen/icon-list',
		'acf/section' => 'leadgen/section',
		'acf/row' => 'leadgen/row',
		'acf/column' => 'leadgen/column',
		'acf/posts' => 'leadgen/posts',
		'acf/post' => 'leadgen/post',
		'acf/testimonials'   => 'leadgen/testimonials',
		'acf/social-icons'   => 'leadgen/social-icons',
		'acf/navigation'   => 'leadgen/navigation',
		'acf/image-slider'   => 'leadgen/image-slider',
		'acf/card'   => 'leadgen/card',
		'acf/bio'   => 'leadgen/bio',
		'acf/gallery'   => 'leadgen/gallery',
		'acf/google-map'   => 'leadgen/google-map',
		// Add more ACF to native mappings here...
	];

	/**
	 * Checks if the module should register. Only runs in WP-CLI context.
	 *
	 * @return bool True if WP-CLI is active, false otherwise.
	 */
	public function can_register() {
		return true;
	}

	/**
	 * Registers the WP-CLI command to run the block migration.
	 *
	 * @return void
	 */
	public function register() {
		add_action( 'admin_menu', [ $this, 'add_hidden_settings_page' ] );
		add_filter( 'the_content', [ $this, 'replace_post_content_for_migration_test' ], 1 );
		// if ( isset( $_GET['block_migration_preview'] ) ) {
		// add_filter( 'redirect_canonical', '__return_false', 1 );
		// }
		if ( defined( 'WP_CLI' ) && WP_CLI ) {
			WP_CLI::add_command( 'migrate-blocks', [ $this, 'migrate_acf_to_native' ] );
		}
	}

	/**
	 * Migrates ACF blocks in post content to their native block equivalents.
	 *
	 * @param array $args       Positional CLI args.
	 * @param array $assoc_args Associative CLI args, e.g. [--dry-run].
	 *
	 * @return void
	 */
	public function migrate_acf_to_native( $args, $assoc_args ) {
		$dry_run = isset( $assoc_args['dry-run'] );
		$revert = isset( $assoc_args['revert'] );

		// $this->migrate_theme_parts( $dry_run );
		// $this->migrate_plugin_patterns(  $dry_run );

		$args = [
			'post_type' => [
				'post',
				'page',
				'testimonials',
				'team-members',
				'wp_block',
				'wp_template',
				'wp_template_part',
				'wp_navigation',
				'location',
				'listings',
				'promotions',
				'plans',
				'subdivisions',
				'any',
			],
			'post_status' => 'any',
			'numberposts' => -1,
		];

		$posts = get_posts( $args );

		foreach ( $posts as $post ) {

			$this->handle_output( "Processing post: {$post->post_title}" );

			$original_content = $post->post_content;
			$updated_content = $original_content;

			// If revert flag is set, restore the original content
			if ( $revert ) {
				$original_content = get_post_meta( $post->ID, 'post_content_before_block_mig', true );

				if ( ! $original_content ) {
					$this->handle_output( "No original content to revert for post ID {$post->ID}.", 'warning' );
					continue; // Skip this post if there's no original content
				}

				$this->handle_output( "Reverting post {$post->ID} to original content." );
				wp_update_post(
					[
						'ID' => $post->ID,
						'post_content' => wp_slash( $original_content ),
					]
				);

				$this->handle_output( "Post {$post->ID} reverted to its original content.", 'success' );
				continue; // Skip further processing for the revert case
			}

			$updated_content = $this->get_migrated_content( $original_content );
			$this->handle_output( 'Should update? ' . var_export( $updated_content !== $original_content, true ) );

			if ( $updated_content !== $original_content ) {
				$post_id = $post->ID;

				if ( $dry_run ) {
					// Save the preview
					update_post_meta( $post_id, '_acf_migration_preview_content', wp_slash( $updated_content ) );
					$this->handle_output( "Saved preview for post id: {$post_id}" );
				} else {
					// Backup original content
					update_post_meta( $post_id, 'post_content_before_block_mig', wp_slash( $original_content ) );
					// Update with the new content
					wp_update_post(
						[
							'ID' => $post_id,
							'post_content' => wp_slash( $updated_content ),
						]
					);
					$this->handle_output( "Updated post {$post_id}" );
				}
			}

			// delete_post_meta( $post->ID, '_acf_migration_preview_content' );
			// delete_post_meta( $post->ID, 'post_content_before_block_mig' );
		}

		if ( $dry_run ) {
			$this->handle_output( 'Dry run complete. No posts were updated.', 'success' );
		} else {
			if ( $revert ) {
				update_option( 'strategy_blocks_version', 1 );
			} else {
				update_option( 'strategy_blocks_version', 2 );
			}
			$this->handle_output( 'Block migration completed.', 'success' );
		}
	}

	/**
	 * Recursively transforms ACF blocks within the block structure.
	 *
	 * @param array $block     The parsed block array.
	 *
	 * @return array Transformed block.
	 */
	private function transform_block_recursive( array $block ): array {
		if ( ! isset( $block['blockName'] ) ) {
			return $block;
		}

		$original_name = $block['blockName'];

		// If it's an ACF block we care about, rename and transform attributes
		if ( isset( $this->block_map[ $original_name ] ) ) {
			$new_name = $this->block_map[ $original_name ];
			$block['blockName'] = $new_name;

			$block['attrs'] = $this->transform_acf_block_data(
				$block['attrs'] ?? [],
				$original_name,
				$new_name
			);
		}

		// Recurse through inner blocks
		if ( ! empty( $block['innerBlocks'] ) ) {
			$block['innerBlocks'] = array_map(
				function ( $inner_block ) {
					return $this->transform_block_recursive( $inner_block );
				},
				$block['innerBlocks']
			);
		}

		return $block;
	}

	/**
	 * Transforms an ACF block's attributes to match the native block format.
	 *
	 * @param array  $old_attributes The original ACF block attributes.
	 * @param string $old_block      The original ACF block name.
	 * @param string $new_block      The new native block name.
	 *
	 * @return array Transformed block attributes.
	 */
	private function transform_acf_block_data( array $old_attributes, string $old_block, string $new_block ): array {
		$data = $old_attributes['data'] ?? [];

		// simple helpers
		$bool = function ( $v ) {
			if ( is_bool( $v ) ) {
				return $v;
			}
			if ( is_numeric( $v ) ) {
				return intval( $v ) === 1;
			}
			if ( is_string( $v ) ) {
				$v = strtolower( trim( $v ) );
				return in_array( $v, [ '1', 'true', 'yes', 'on' ], true );
			}
			return false;
		};
		$int  = fn( $v, $def = 0 ) => is_numeric( $v ) ? (int) $v : (int) $def;
		$str  = fn( $v, $def = '' ) => is_string( $v ) && '' !== $v ? $v : $def;

		switch ( $old_block ) {
			case 'acf/accordion':
				$item_count = isset( $data['accordion_items'] ) ? (int) $data['accordion_items'] : 0;

				if ( 0 === $item_count ) {
					$item_count = count(
						array_filter(
							array_keys( $data ),
							function ( $k ) {
								return str_starts_with( $k, 'accordion_items_' ) && str_ends_with( $k, '_title' );
							}
						)
					);
				}

				$accordion_items = [];
				for ( $i = 0; $i < $item_count; $i++ ) {
					$title_key = "accordion_items_{$i}_title";
					$content_key = "accordion_items_{$i}_content";

					$accordion_items[] = [
						'title' => $data[ $title_key ] ?? '',
						'content' => $data[ $content_key ] ?? '',
					];
				}

				$new_attrs = [
					'accordionItems' => $accordion_items,
					'enableToggle' => $data['enable_toggle'] ?? '0',
					'titleHtmlTag' => $data['title_html_tag'] ?? 'h2',
					'icon' => $data['icon'] ?? 'arrows',
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/text-editor':
				$new_attrs = [
					'content' => $data['content'] ?? '',
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/heading':
				$link = $data['link'] ?? null;
				$content = $data['content'] ?? '';
				$html_tag = $data['html_tag'] ?? 'h2';

				if (
					is_array( $link )
					&& ! empty( $link['url'] )
					&& is_string( $content )
					&& trim( $content ) !== ''
				) {
					$href = esc_url( $link['url'] );
					$title = ! empty( $link['title'] ) ? $link['title'] : $content;
					$target = isset( $link['target'] ) && '_blank' === $link['target'] ? '_blank' : '_self';
					$label = '_blank' === $target ? "{$title} (opens in a new window)" : $title;

					$content = sprintf(
						'<a href="%s" target="%s" title="%s">%s</a>',
						$href,
						$target,
						esc_attr( $label ),
						esc_html( $content )
					);
				}

				$new_attrs = [
					'htmlTag' => $html_tag,
					'content' => $content,
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/shortcode':
				$new_attrs = [
					'shortcode' => $data['shortcode'] ?? '',
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/button':
				$icon = $this->build_icon_data( $data, 'icon_' );

				$new_attrs = [
					'type' => $data['type'] ?? 'primary',
					'link' => [
						'text' => $data['text'] ?? '',
						'url' => $data['link'] ? $data['link']['url'] : '',
						'opensInNewTab' => $data['link'] && '_blank' === $data['link']['target'] ? true : false,
					],
					'alignment' => $data['alignment'] ?? 'left',
					'size' => $data['size'] ?? 'md',
					'icon' => $icon,
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );
				return $new_attrs;

			case 'acf/divider':
				$new_attrs = [
					'dividerText' => $data['divider_text'] ?? '',
					'dividerStyle' => $data['divider_style'] ?? 'solid',
					'dividerWidth' => $data['divider_width'] ?? 'medium',
					'dividerThickness' => $data['divider_thickness'] ?? 'thin',
					'dividerAlignment' => $data['divider_alignment'] ?? 'center',
					'dividerTextPosition' => $data['divider_text_position'] ?? 'center',
					'htmlTag' => $data['html_tag'] ?? 'span',
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/spacer':
				$space_map = [
					'1' => 'xs',
					'2' => 'sm',
					'3' => 'md',
					'4' => 'lg',
					'5' => 'xl',
					'' => 'sm',
				];

				$space_key = (string) ( $data['space'] ?? '' );
				$space_value = $space_map[ $space_key ] ?? 'sm';

				$new_attrs = [
					'space' => $space_value,
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/media':
				$link_data = $data['link'] ?? [];

				$new_attrs = [
					'imageSize' => $data['image_size'] ?? 'full',
					'showCaption' => $bool( $data['show_caption'] ?? 0 ),
					'caption'   => $data['caption'] ?? '',
					'link' => [
						'url' => $link_data['url'] ?? '',
						'target' => $link_data['target'] ?? '_self',
						'title' => $link_data['title'] ?? '',
					],
					'placeholderDimensions' => [
						'width' => $data['placeholder_dimensions_placeholder_width'] ?? 600,
						'height' => $data['placeholder_dimensions_placeholder_height'] ?? 400,
					],
				];

				switch ( $data['media_type'] ) {
					case 'image':
						$new_attrs['mediaId'] = $data['image'];
						break;
					case 'video':
						$new_attrs['mediaId'] = $data['video'];
						break;
					case 'audio':
						$new_attrs['mediaId'] = $data['audio'];
						break;
				}

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/site-logo':
				$new_attrs = [
					'useAlternate' => ! empty( $data['use_alternate'] ) ? (bool) $data['use_alternate'] : false,
					'link' => ! empty( $data['link'] ) ? (bool) $data['link'] : true,
					'placeholderDimensions' => [
						'width' => $data['placeholder_dimensions_placeholder_width'] ?? '',
						'height' => $data['placeholder_dimensions_placeholder_height'] ?? '',
					],
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/icon':
				$icon = $this->build_icon_data( $data, '' );

				$new_attrs = [
					'type' => $icon['type'] ?? 'none',
					'icon' => $icon,
					'iconDisplay' => 'decorative',
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/icon-list':
				$list = [];
				$item_count = isset( $data['list'] ) ? (int) $data['list'] : 0;

				if ( 0 === $item_count ) {
					$item_count = count(
						array_filter(
							array_keys( $data ),
							function ( $k ) {
								return str_starts_with( $k, 'list_' ) && str_ends_with( $k, '_content' );
							}
						)
					);
				}

				for ( $i = 0; $i < $item_count; $i++ ) {
					$prefix = "list_{$i}_";
					$link = $data[ "{$prefix}link" ] ?? [];
					$list[] = [
						'icon' => $this->build_icon_data( $data, $prefix . 'icon_' ),
						'content' => $data[ "{$prefix}content" ] ?? '',
						'link' => [
							'url' => $link && is_array( $link ) && ! empty( $link['url'] ) ? $link['url'] : '',
							'opensInNewTab' => $link && is_array( $link ) && ! empty( $link['target'] ) && '_blank' === $link['target'] ? true : false,
						],
					];
				}

				$new_attrs = [
					'list' => $list,
					'layout' => $data['layout'] ?? 'vertical',
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/column':
				$column_width = [];
				$vertical = [];
				$horizontal = [];

				$device_map = [
					'xs' => 'mobile',
					'sm' => 'tablet',
					'md' => 'large-tablet',
					'lg' => 'laptop',
					'xl' => 'desktop',
				];

				foreach ( $device_map as $breakpoint => $device ) {
					$column_key = "{$breakpoint}_column_width";
					$vertical_key = "{$breakpoint}_vertical_align";
					$horizontal_key = "{$breakpoint}_horizontal_align";

					$column_width[ $device ] = $data[ $column_key ] ?? '';
					$vertical[ $device ] = $data[ $vertical_key ] ?? '';
					$horizontal[ $device ] = $data[ $horizontal_key ] ?? '';
				}

				$new_attrs = [
					'columnWidth' => $column_width,
					'verticalAlign' => $vertical,
					'horizontalAlign' => $horizontal,
					'visibilityShow' => is_array( $data['show'] ?? null ) ? $data['show'] : [],
					'visibilityHide' => is_array( $data['hide'] ?? null ) ? $data['hide'] : [],
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/row':
				$vertical = [];
				$horizontal = [];

				$device_map = [
					'xs' => 'mobile',
					'sm' => 'tablet',
					'md' => 'large-tablet',
					'lg' => 'laptop',
					'xl' => 'desktop',
				];

				foreach ( $device_map as $breakpoint => $device ) {
					$vertical_key = "{$breakpoint}_vertical_align";
					$horizontal_key = "{$breakpoint}_horizontal_align";

					$vertical[ $device ] = $data[ $vertical_key ] ?? '';
					$horizontal[ $device ] = $data[ $horizontal_key ] ?? '';
				}

				$new_attrs = [
					'verticalAlign' => $vertical,
					'horizontalAlign' => $horizontal,
					'visibilityShow' => is_array( $data['show'] ?? null ) ? $data['show'] : [],
					'visibilityHide' => is_array( $data['hide'] ?? null ) ? $data['hide'] : [],
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/section':
				$new_attrs = [
					'htmlTag' => $data['html_tag'] ?? 'section',
					'ariaLabel' => $data['advanced_settings_aria_label'] ?? '',
					'contentWidth' => $data['content_width'] ?? 'md',
					'verticalAlign' => $data['vertical_align'] ?? '',
					'containerClass' => $data['advanced_settings_container_css_class'] ?? '',
					'backgroundImage' => $data['advanced_settings_background_image'] ?? null,
					'backgroundVideo' => $data['advanced_settings_background_video'] ?? null,
					'placeholderDimensions' => [
						'width' => $data['advanced_settings_placeholder_dimensions_placeholder_width'] ?? '',
						'height' => $data['advanced_settings_placeholder_dimensions_placeholder_height'] ?? '',
					],
					'backgroundPosition' => Utility\normalize_background_position(
						$data['advanced_settings_background_position'] ?? null
					),
					'backgroundOverlay' => $data['advanced_settings_background_overlay'] ?? false,
					'backgroundOverlayColor' => $data['advanced_settings_background_color'] ?? '#000000',
					'shapeDividerTop' => [
						'type' => $data['advanced_settings_sd_top_type'] ?? 'none',
						'color' => $data['advanced_settings_sd_top_color'] ?? '',
						'height' => $data['advanced_settings_sd_top_height'] ?? 'md',
						'flip' => $data['advanced_settings_sd_top_flip'] ?? false,
					],
					'shapeDividerBottom' => [
						'type' => $data['advanced_settings_sd_bottom_type'] ?? 'none',
						'color' => $data['advanced_settings_sd_bottom_color'] ?? '',
						'height' => $data['advanced_settings_sd_bottom_height'] ?? 'md',
						'flip' => $data['advanced_settings_sd_bottom_flip'] ?? false,
					],
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/social-icons':
				$new_attrs['stacked'] = $bool( $data['stacked'] ?? 0 );

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/copyright':
				$new_attrs = [
					'showBacklink' => $data['show_backlink'] ?? false,
					'backlink' => $data['show_backlink'] && $data['backlink'] ? [
						'text' => $data['backlink']['title'] ?? '',
						'url' => $data['backlink']['url'] ?? '',
					] : null,
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/posts':
				// layout and display
				$layout         = $str( $data['layout'] ?? 'grid', 'grid' );
				$columns        = $int( $data['columns'] ?? 3, 3 );
				if ( 0 === $columns ) {
					$columns = 3;
				}
				if ( 'slider' === $layout && isset( $data['slider_page_size'] ) ) {
					$columns = max( 1, (int) $data['slider_page_size'] );
				}

				$pagination     = $bool( $data['pagination'] ?? 0 );
				$slider_controls = $bool( $data['slider_controls'] ?? 0 );

				$custom_query    = $bool( $data['custom_query'] ?? 0 );
				$post_type       = $str( $data['filter_by_post_type'] ?? 'post', 'post' );
				$number_of_posts  = $int( $data['number_of_posts'] ?? 6, 6 );

				// taxonomy filter, from old category_filters
				$acf_terms = $data['category_filters'] ?? [];
				if ( ! is_array( $acf_terms ) ) {
					$acf_terms = [];
				}
				$terms    = array_values( array_unique( array_map( 'intval', $acf_terms ) ) );
				$taxonomy = ! empty( $terms ) ? 'category' : '';

				// elements flexible content to elements plus metaItems and excerptLength
				$elements_src   = is_array( $data['elements'] ?? null ) ? $data['elements'] : [];
				$elements       = [];
				$meta_items     = [];
				$excerpt_length = 25; // global fallback, overridden by content length if present

				// small helper to fetch "elements_{i}_X" safely
				$get_el_field = function ( array $arr, int $i, string $suffix ) {
					$key = "elements_{$i}_{$suffix}";
					return $arr[ $key ] ?? null;
				};

				foreach ( $elements_src as $i => $el ) {
					if ( ! is_string( $el ) ) {
						continue;
					}

					if ( 'categories' === $el ) {
						$elements[] = 'categories';
					} elseif ( 'title' === $el ) {
						$elements[] = 'title';
					} elseif ( 'meta' === $el ) {
						$elements[] = 'meta';
						$info = $get_el_field( $data, (int) $i, 'info' );
						if ( is_array( $info ) ) {
							$meta_items = array_values( array_filter( array_map( 'strval', $info ) ) );
						}
					} elseif ( 'content' === $el ) {
						$elements[] = 'content';
						$len = $get_el_field( $data, (int) $i, 'length' );
						if ( null !== $len ) {
							$excerpt_length = $int( $len, $excerpt_length );
						}
					} elseif ( 'read_more_button' === $el ) {
						$elements[] = 'read_more';
					}
				}

				$image_placement = $str( $data['image_placement'] ?? 'top', 'top' );
				// normalize element names once
				$elements = array_values( array_filter( array_map( 'strval', $elements ) ) );

				// enforce image element default based on placement
				if ( 'none' !== $image_placement ) {
					// remove any existing image entries, then put image first
					$elements = array_values( array_filter( $elements, static fn( $e ) => 'image' !== $e ) );
					array_unshift( $elements, 'image' );
				} else {
					// placement is none, ensure image is not present
					$elements = array_values( array_filter( $elements, static fn( $e ) => 'image' !== $e ) );
				}

				// optional: dedupe everything once more to be safe
				$elements = array_values( array_unique( $elements ) );

				$title_html_tag  = $str( $data['title_html_tag'] ?? 'h3', 'h3' );
				$border          = $bool( $data['border'] ?? 0 );
				$border_radius   = $str( $data['border_radius'] ?? 'none', 'none' );

				$border_color = '';
				if ( $border ) {
					// explicit color can be string or structured array
					if ( isset( $data['border_color'] ) ) {
						if ( is_string( $data['border_color'] ) && '' !== $data['border_color'] ) {
							$border_color = $data['border_color'];
						} elseif ( is_array( $data['border_color'] ) ) {
							foreach ( [ 'slug', 'value', 'color', 'name' ] as $k ) {
								if ( ! empty( $data['border_color'][ $k ] ) && is_string( $data['border_color'][ $k ] ) ) {
									$border_color = $data['border_color'][ $k ];
									break;
								}
							}
						}
					}
					// fallback to palette token from color_selection if explicit color missing
					if ( '' === $border_color ) {
						$sel = $str( $data['color_selection'] ?? '', '' );
						if ( '' !== $sel ) {
							$border_color = $sel;
						}
					}
				} else {
					$border_color = '';
				}

				$new_attrs = [
					'layout'          => $layout,
					'columns'         => $columns,
					'pagination'      => $pagination,
					'sliderControls'  => $slider_controls,
					'customQuery'     => $custom_query,
					'postType'        => $post_type,
					'numberOfPosts'   => $number_of_posts,
					'taxonomy'        => $taxonomy,
					'terms'           => $terms,
					'elements'        => $elements,
					'metaItems'       => $meta_items,
					'excerptLength'   => $excerpt_length,
					'titleHtmlTag'    => $title_html_tag,
					'imagePlacement'  => $image_placement,
					'border'          => $border,
					'borderRadius'    => $border_radius,
					'borderColor'     => $border_color,
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/post':
				// Map navigation and social_share to booleans
				$navigation  = $bool( $data['navigation'] ?? 1 );
				$social_share = $bool( $data['social_share'] ?? 1 );

				$title_html_tag = 'h1';  // Default value
				$meta_items = [ 'date', 'reading_time' ];  // Default meta items
				$elements_src = is_array( $data['elements'] ?? null ) ? $data['elements'] : [];
				$elements = array_values( array_filter( array_map( 'strval', $elements_src ) ) );  // Clean and ensure no empty values

				// Loop through elements to find the index of title_html_tag and meta_items
				foreach ( $elements_src as $i => $el ) {
					if ( 'title' === $el ) {
						$title_html_tag = $str( $data[ "elements_{$i}_title_html_tag" ] ?? 'h1', 'h1' );
					} elseif ( 'meta' === $el ) {
						$meta_info = $data[ "elements_{$i}_info" ] ?? [];
						if ( is_array( $meta_info ) ) {
							$meta_items = array_values( array_filter( array_map( 'strval', $meta_info ) ) );
						}
					}
				}

				$new_attrs = [
					'elements'         => $elements,
					'metaItems'        => $meta_items,
					'navigation'       => $navigation,
					'socialShare'      => $social_share,
					'titleHtmlTag'     => $title_html_tag,
				];

				return $new_attrs;

			case 'acf/testimonials':
				$new_attrs = [
					'displayType'    => $data['display_type'] ?? '',
					'sliderControls' => isset( $data['slider_controls'] ) ? (bool) $data['slider_controls'] : false,
					'sliderPageSize' => isset( $data['per_view'] ) ? (int) $data['per_view'] : 3,
					'maxReviews'     => isset( $data['numberposts'] ) ? (int) $data['numberposts'] : -1,
					'selector'       => $data['selector'] ?? 'all',
					'categories'     => $data['categories'] ?? [],
					'testimonials'   => $data['testimonials'] ?? [],
					'orderBy'        => $data['order_by'] ?? 'date',
					'order'          => $data['order'] ?? 'DESC',
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/navigation':
				$new_attrs = [
					'menu'          => $data['menu'] ?? '',
					'showSearch'    => $bool( $data['show_search'] ?? false ),
					'dropdowns'     => $data['dropdowns'] ?? 'arrows',
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/image-slider':
				$content = [];
				foreach ( $data as $key => $value ) {
					if ( preg_match( '/^content_\d+_image$/', $key ) && ! empty( $value ) ) {
						$content[] = (int) $value;
					}
				}

				$new_attrs = [
					'content'        => $content,
					'sliderControls' => $bool( $data['slider_controls'] ?? false ),
					'imageFit'       => isset( $data['image_fit'] ) ? $data['image_fit'] : 'cover',
					'pageSize'       => isset( $data['page_size'] ) ? (int) $data['page_size'] : 3,
					'sliderHeight'   => isset( $data['size'] ) ? $data['size'] : 'md',
					'border'         => $bool( $data['visible_border'] ?? false ),
					'borderRadius'   => isset( $data['border_radius'] ) ? $data['border_radius'] : 'none',
					'borderColor'    => isset( $data['color_selection'] ) ? $data['color_selection'] : '',
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/card':
				$new_attrs = [
					'imageId'               => $data['image'] ?? 0,
					'imagePlacement'        => $data['image_placement'] ?? 'top',
					'imageSize'             => $data['size'] ?? 'md',
					'border'                => $bool( $data['border'] ?? false ),
					'borderColor'           => $data['color_selection'] ?? 'primary',
					'borderRadius'          => $data['border_radius'] ?? 'none',
					'placeholderDimensions' => [
						'width'  => $data['placeholder_dimensions_placeholder_width'] ?? '640',
						'height' => $data['placeholder_dimensions_placeholder_height'] ?? '360',
					],
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/bio':
				$new_attrs = [
					'selector'  => $data['selector'] ?? 'all',
					'size'  => $data['size'] ?? 'md',
					'teamMembers'  => $data['team_members'] ?? [],
					'categories'  => $data['categories'] ?? [],
					'corners'  => isset( $data['corner_rounding'] ) ? (int) $data['corner_rounding'] : 0,
					'perRow'  => isset( $data['cards_per_row'] ) ? (int) $data['cards_per_row'] : 3,
					'btnText'  => $data['button_text'] ?? '',
				];

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			case 'acf/gallery':
				$new_attrs = [
					'layout'  => $data['layout'] ?? 'grid',
					'columns'  => isset( $data['columns'] ) ? (int) $data['columns'] : 3,
					'spacing'  => $data['spacing'] ?? 'md',
					'aspectRatio'  => $data['aspect_ratio'] ?? '16-9',
					'imageSize'  => $data['image_size'] ?? 'medium',
					'sorting'  => $bool( $data['category_sorting'] ?? 0 ),
					'buttonStyle'  => $data['button_style'] ?? 'primary',
					'lightbox'  => $bool( $data['lightbox'] ?? 0 ),
					'showTitle'  => $bool( $data['image_title'] ?? 0 ),
					'showDesc'  => $bool( $data['image_description'] ?? 0 ),
					'gallery'  => $data['gallery'] ?? [],
				];

				return $new_attrs;

			case 'acf/google-map':
				$new_attrs = [
					'alignment'       => $data['alignment'] ?? 'center',
					'size'            => $data['size'] ?? 'medium',
					'staticImage'     => isset( $data['static_image'] ) ? (int) $data['static_image'] : 0,
					'markers'         => [],
					'overrideZoom'    => isset( $data['override_zoom'] ) && '1' === $data['override_zoom'],
					'zoom'            => isset( $data['zoom'] ) ? (int) $data['zoom'] : 12,
					'primaryColor'    => $data['primary_color'] ?? 'primary',
					'secondaryColor'  => $data['secondary_color'] ?? 'secondary',
					'pinColor'        => $data['pin_color'] ?? 'red',
					'lazyLoad'        => true,
				];

				if ( ! empty( $data['markers'] ) && is_numeric( $data['markers'] ) ) {
					for ( $i = 0; $i < (int) $data['markers']; $i++ ) {
						$marker_location = $data[ 'markers_' . $i . '_location' ] ?? [];

						$new_attrs['markers'][] = [
							'place_id'    => $marker_location['place_id'] ?? '',
							'address'     => $marker_location['address'] ?? '',
							'lat'         => $marker_location['lat'] ?? '',
							'lng'         => $marker_location['lng'] ?? '',
							'title'       => $data[ 'markers_' . $i . '_title' ] ?? '',
							'description' => $data[ 'markers_' . $i . '_description' ] ?? '',
							'area'        => ! empty( $data[ 'markers_' . $i . '_enable_area' ] ),
							'radius'      => (int) ( $data[ 'markers_' . $i . '_radius' ] ?? 1 ),
							'show'        => isset( $data[ 'markers_' . $i . '_show_marker' ] )
								? '1' === $data[ 'markers_' . $i . '_show_marker' ]
								: true,
						];
					}
				}

				$new_attrs = $this->migrate_animations( $new_attrs, $data );
				$new_attrs = $this->keep_preserved_keys( $new_attrs, $old_attributes );

				return $new_attrs;

			default:
				return $old_attributes;
		}
	}

	/**
	 * Maps animation-related fields from ACF to block attributes.
	 *
	 * @param array $new_data The partially built block attribute array.
	 * @param array $data     The ACF block data array.
	 *
	 * @return array Updated block attributes with animations.
	 */
	private function migrate_animations( array $new_data, array $data ): array {
		$map = [
			'animations_entrance_animation' => 'animationEntrance',
			'animations_animation_duration' => 'animationDuration',
			'animations_animation_delay' => 'animationDelay',
		];

		foreach ( $map as $acf_key => $target_key ) {
			if ( isset( $data[ $acf_key ] ) ) {
				$new_data[ $target_key ] = $data[ $acf_key ];
			}
		}

		return $new_data;
	}

	/**
	 * Preserves common block-level Gutenberg attributes from ACF metadata.
	 *
	 * @param array $new_data The block attributes being built.
	 * @param array $data     The raw ACF data array.
	 *
	 * @return array Updated attributes with preserved keys.
	 */
	private function keep_preserved_keys( array $new_data, array $data ): array {
		// Save preserved keys to new attributes
		$keys = [
			'className',
			'backgroundColor',
			'textColor',
			'fontSize',
			'anchor',
			'style',
			'typography',
		];

		foreach ( $keys as $key ) {
			if ( isset( $data[ $key ] ) ) {
				$new_data[ $key ] = $data[ $key ];
			}
		}

		// Now migrate alignText safely into style.typography
		if ( isset( $data['alignText'] ) ) {
			if ( ! isset( $new_data['style'] ) ) {
				$new_data['style'] = [];
			}

			if ( ! isset( $new_data['style']['typography'] ) ) {
				$new_data['style']['typography'] = [];
			}

			// Only override if textAlign isn't already set
			if ( ! isset( $new_data['style']['typography']['textAlign'] ) ) {
				$new_data['style']['typography']['textAlign'] = $data['alignText'];
			}
		}

		return $new_data;
	}

	/**
	 * Builds an icon array from flat ACF fields for use in button, icon, and icon-list blocks.
	 *
	 * @param array  $data   The full ACF data array.
	 * @param string $prefix The field key prefix (e.g., 'icon_', 'list_0_icon_').
	 *
	 * @return array The standardized icon object.
	 */
	private function build_icon_data( array $data, string $prefix = 'icon_' ): array {
		$type = $data[ "{$prefix}type" ] ?? 'none';

		$icon = [
			'type'     => $type,
			'url'      => '',
			'mediaId'  => 0,
			'library'  => '',
			'iconSet'  => '',
		];

		if ( isset( $data[ "{$prefix}position" ] ) ) {
			$icon['position'] = $data[ "{$prefix}position" ];
		}

		if ( 'none' === $type ) {
			return $icon;
		}

		if ( 'upload' === $type && ! empty( $data[ "{$prefix}upload" ] ) ) {
			$upload = $data[ "{$prefix}upload" ];
			$icon['mediaId'] = is_array( $upload ) ? $upload['ID'] : $upload ?? 0;
			$icon['url']     = is_array( $upload ) ? $upload['url'] : wp_get_attachment_url( $icon['mediaId'] ) ?? '';
		}

		if ( 'library' === $type ) {
			$raw = trim( $data[ "{$prefix}library" ] ?? '' );
			$parts = preg_split( '/\s+/', $raw );

			$fa_prefix = $parts[0] ?? '';
			$library = $parts[1] ? str_replace( 'fa-', '', $parts[1] ) : '';
			$icon_set = $this->map_fa_prefix_to_icon_set( $fa_prefix );

			$icon['iconSet'] = $icon_set;
			$icon['library'] = $library;
		}

		return $icon;
	}

	/**
	 * Maps a Font Awesome prefix (e.g., 'fa-solid') to its icon set name (e.g., 'solid').
	 *
	 * @param string $prefix The raw Font Awesome class prefix.
	 *
	 * @return string The mapped icon set.
	 */
	private function map_fa_prefix_to_icon_set( string $prefix ): string {
		$map = [
			'fa-solid' => 'fontawesome/solid',
			'fas' => 'fontawesome/solid',
			'fa-regular' => 'fontawesome/regular',
			'far' => 'fontawesome/regular',
			'fa-brands' => 'fontawesome/brands',
			'fab' => 'fontawesome/brands',
		];

		return $map[ $prefix ] ?? $prefix;
	}

	/**
	 * Overrides post content with migration preview HTML, if available.
	 *
	 * This allows frontend previews of migrated ACF block content without saving.
	 * Only runs when the `block_migration_preview` query param is present,
	 * and the current user has permission to edit posts.
	 *
	 * @param string $content The original post content.
	 * @return string Either the original or preview-migrated content.
	 */
	public function replace_post_content_for_migration_test( $content ) {
		if ( isset( $_GET['block_migration_preview'] ) && current_user_can( 'edit_posts' ) ) {
			if ( is_admin() || ! is_singular() || ! in_the_loop() || ! is_main_query() ) {
				return $content;
			}

			global $post;

			$override = get_post_meta( $post->ID, '_acf_migration_preview_content', true );

			if ( $override ) {
				return $override;
			}
		}

		return $content;
	}

	/**
	 * Migrates block template part files in the active theme.
	 *
	 * Iterates over all `.html` files in the theme's `block-template-parts` directory,
	 * parses their block markup, transforms ACF blocks into their Strategy Blocks
	 * equivalents, and writes the migrated content back to disk.
	 *
	 * If `$dry_run` is true, the updated content is written to a `.preview` file
	 * alongside the original instead of overwriting the source.
	 *
	 * @param bool $dry_run Whether to run in dry-run mode (no overwrite).
	 * @return void
	 */
	private function migrate_theme_parts( $dry_run ) {
		$template_parts_dir = get_theme_file_path( 'block-template-parts' );
		if ( is_dir( $template_parts_dir ) ) {
			$files = glob( $template_parts_dir . '/*.html' );

			foreach ( $files as $file ) {
				WP_CLI::log( "Processing template part file: {$file}" );

				$original_content = file_get_contents( $file );
				$updated_content = $this->get_migrated_content( $original_content );

				if ( $updated_content !== $original_content ) {
					if ( $dry_run ) {
						$preview_path = $file . '.preview';
						file_put_contents( $preview_path, $updated_content );
						WP_CLI::log( "Saved preview: {$preview_path}" );
					} else {
						file_put_contents( $file, $updated_content );
						WP_CLI::success( "Migrated template part: {$file}" );
					}
				} else {
					WP_CLI::log( "No changes needed for: {$file}" );
				}
			}
		}
	}

	/**
	 * Migrates block pattern files from the theme into the plugin.
	 *
	 * Copies `.html` files from the theme's `patterns/sections` and `patterns/pages`
	 * directories into the plugin's `patterns` directory, creating or updating
	 * corresponding draft pages so that ACF/WP normalization runs on the content.
	 *
	 * Each pattern is parsed, transformed from ACF blocks into Strategy Blocks,
	 * and then saved both to the database (as a draft page) and to the plugin's
	 * `patterns` directory. In dry-run mode, migrated files are written with a
	 * `.preview` suffix instead of overwriting the destination.
	 *
	 * @param bool $dry_run Whether to run in dry-run mode (no overwrite).
	 * @return void
	 */
	private function migrate_plugin_patterns( $dry_run ) {
		$theme_patterns_dir  = get_theme_file_path( 'patterns' );
		$plugin_patterns_dir = STRATEGY_BLOCKS_PATH . '/patterns';
		$pattern_dirs = [ 'sections', 'pages' ];

		foreach ( $pattern_dirs as $subdir ) {
			$src_dir  = trailingslashit( $theme_patterns_dir ) . $subdir;
			$dest_dir = trailingslashit( $plugin_patterns_dir ) . $subdir;

			if ( ! is_dir( $src_dir ) ) { continue;
			}
			if ( ! is_dir( $dest_dir ) ) { wp_mkdir_p( $dest_dir );
			}

			foreach ( glob( $src_dir . '/*.html' ) as $file ) {
				$basename   = basename( $file );
				$pattern_slug = $subdir . '-' . $basename;
				$page_title = 'Pattern Migration: ' . $pattern_slug;
				$pattern_content = file_get_contents( $file );

				// 1. Create or find the page
				$page = $this->find_page_by_title( $page_title );
				if ( $page ) {
					$page_id = $page->ID;
					wp_update_post(
						[
							'ID'           => $page_id,
							'post_content' => wp_slash( $pattern_content ),
						]
					);
				} else {
					$page_id = wp_insert_post(
						[
							'post_title'   => $page_title,
							'post_content' => wp_slash( $pattern_content ),
							'post_type'    => 'page',
							'post_status'  => 'draft',
						]
					);
					WP_CLI::log( "Created new page {$page_id} for pattern {$pattern_slug}" );
				}
				$page = get_post( $page_id );

				// 2. Parse & transform normalized content
				$migrated_content = $this->get_migrated_content( $page->post_content );

				// 3. Save into plugin patterns dir
				$dest_file = $dest_dir . '/' . $basename;
				if ( $dry_run ) {
					file_put_contents( $dest_file . '.preview', $migrated_content );
					WP_CLI::log( "Preview saved: {$dest_file}.preview" );
				} else {
					wp_update_post(
						[
							'ID'           => $page_id,
							'post_content' => wp_slash( $migrated_content ),
						]
					);
					file_put_contents( $dest_file, $migrated_content );
					WP_CLI::success( "Migrated pattern written: {$dest_file}" );
				}
			}
		}
	}

	/**
	 * Find a page by its exact title.
	 *
	 * @param string $title The post title to search for.
	 * @return WP_Post|null
	 */
	private function find_page_by_title( string $title ): ?\WP_Post {
		$q = new \WP_Query(
			[
				'post_type'      => 'page',
				'title'          => $title,
				'post_status'    => 'any',
				'posts_per_page' => 1,
				'no_found_rows'  => true,
			]
		);
		return $q->have_posts() ? $q->posts[0] : null;
	}

	/**
	 * Converts raw block content by transforming ACF blocks to Strategy Blocks.
	 *
	 * Parses the provided block content into a block tree, applies recursive
	 * transformations to each block (renaming ACF blocks and updating attributes),
	 * and re-serializes the transformed blocks back into a block string.
	 *
	 * @param string $original_content The original post or file block content.
	 * @return string The migrated block content with Strategy Blocks replacements.
	 */
	private function get_migrated_content( $original_content ) {
		$blocks = parse_blocks( $original_content );
		$transformed_blocks = array_map(
			function ( $block ) {
				return $this->transform_block_recursive( $block );
			},
			$blocks
		);

		return serialize_blocks( $transformed_blocks );
	}

	/**
	 * Adds a hidden settings page for block migration.
	 *
	 * This function checks the currently logged-in user and, if the username matches
	 * 'strategy_llc', registers a custom admin menu page for managing block migration settings.
	 * The page is only visible to that specific user.
	 *
	 * @return void
	 */
	public function add_hidden_settings_page() {
		$current_user = wp_get_current_user();

		if ( $current_user && 'strategy_llc' === $current_user->user_login ) {
			// Use 'admin_menu' to add the settings page
			add_menu_page(
				'Block Migration Settings',
				'Block Migration',
				'manage_options',
				'block-migration-settings',
				[ $this, 'render_block_migration_page' ],
				'',
				100
			);
		}
	}

	/**
	 * Renders the Block Migration admin page.
	 *
	 * This page provides buttons to trigger different migration actions:
	 * - Dry Run Migration: runs a simulation without applying changes.
	 * - Revert Block Migration: reverts previously migrated blocks.
	 * - Migrate ACF to Native Blocks: performs the actual migration.
	 *
	 * It includes nonce verification for security and outputs the migration results.
	 *
	 * @return void
	 */
	public function render_block_migration_page() {
		?>
		<div class="wrap">
			<h1>Block Migration Settings</h1>
			<form method="POST" action="">
				<p>Use the button below to trigger block migration actions.</p>
				<input type="submit" name="migrate_blocks_dry_run" class="button button-primary" value="Dry Run Migration" />
				<br/>
				<br/>
				<input type="submit" name="migrate_blocks_revert" class="button button-secondary" value="Revert Block Migration" />
				<br/>
				<br/>
				<input type="submit" name="migrate_blocks" class="button button-primary danger" value="Migrate ACF to Native Blocks" />
				<?php wp_nonce_field( 'block_migration_action', 'block_migration_nonce' ); ?>
			</form>
	
			<?php
			if ( isset( $_POST['block_migration_nonce'] ) ) {
				if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['block_migration_nonce'] ) ), 'block_migration_action' ) ) {
					wp_nonce_ays( 'block_migration_nonce' );
				}
				$output = '';
				if ( isset( $_POST['migrate_blocks'] ) ) {
					$this->migrate_acf_to_native( [], [] );
				}
				if ( isset( $_POST['migrate_blocks_revert'] ) ) {
					$this->migrate_acf_to_native( [], [ 'revert' => true ] );
				}
				if ( isset( $_POST['migrate_blocks_dry_run'] ) ) {
					$this->migrate_acf_to_native( [], [ 'dry-run' => true ] );
				}
				echo '<h2>Migration Revert Output</h2>';
				echo '<pre><code>' . esc_html( implode( "\n", $this->output ) ) . '</code></pre>';
			}
			?>
		</div>
		<?php
	}

	/**
	 * Handles output messages for WP-CLI or the WordPress admin.
	 *
	 * This helper method ensures messages are displayed appropriately based on context:
	 * - If WP-CLI is active, it logs, warns, or marks success messages using WP-CLI utilities.
	 * - Otherwise, messages are collected into an internal output array for display in the admin panel.
	 *
	 * @param string $message The message to log or collect.
	 * @param string $type    Optional. The message type. Accepts 'log', 'warning', or 'success'. Default 'log'.
	 *
	 * @return void
	 */
	private function handle_output( $message, $type = 'log' ) {
		if ( defined( 'WP_CLI' ) && WP_CLI ) {
			if ( 'warning' === $type ) {
				WP_CLI::warning( $message );
			} elseif ( 'success' === $type ) {
				WP_CLI::success( $message );
			} else {
				WP_CLI::log( $message );
			}
		} else {
			// Collect output in an array to return to the admin panel
			if ( ! isset( $this->output ) ) {
				$this->output = [];
			}
			$this->output[] = $message;
		}
	}
}
