<?php
/**
 * Import existing page content into Document tree.
 * Priority: Elementor data → old Aura data → post_content HTML.
 *
 * @package AuraBuilder
 */

declare(strict_types=1);

namespace AuraBuilder\Import;

use AuraBuilder\Document\Document;
use AuraBuilder\Document\Node;

final class ContentImporter {

	public function from_post( int $post_id ): Document {
		$title   = get_the_title( $post_id ) ?: '';
		$title   = is_string( $title ) ? $title : '';
		$parts   = array();
		$sources = array();

		// 1) Elementor (meta may remain even if plugin is off).
		$elementor = get_post_meta( $post_id, '_elementor_data', true );
		if ( is_string( $elementor ) && $elementor !== '' ) {
			$decoded = json_decode( $elementor, true );
			if ( is_array( $decoded ) && $decoded ) {
				$doc = $this->from_elementor( $decoded, $title );
				if ( $this->has_leaf_widgets( $doc ) ) {
					$parts[]   = $doc;
					$sources[] = 'elementor';
				}
			}
		}

		// 2) Old Aura builder meta.
		$old = get_post_meta( $post_id, '_aura_builder_data', true );
		if ( is_array( $old ) && ! empty( $old['sections'] ) ) {
			$doc = $this->from_legacy_aura( $old, $title );
			if ( $this->has_leaf_widgets( $doc ) ) {
				// Prefer full Elementor over sparse legacy; only use if no Elementor.
				if ( ! in_array( 'elementor', $sources, true ) ) {
					$parts[]   = $doc;
					$sources[] = 'aura_legacy';
				}
			}
		}

		// 3) post_content HTML (corpo da página — capa, textos, etc.).
		$post = get_post( $post_id );
		if ( $post && is_string( $post->post_content ) && trim( $post->post_content ) !== '' ) {
			$html = $post->post_content;
			$html = preg_replace( '/<!--\s*\/?wp:.*?-->/s', '', $html ) ?? $html;
			$doc  = $this->from_html( $html, $title );
			if ( $this->has_leaf_widgets( $doc ) ) {
				$parts[]   = $doc;
				$sources[] = 'post_content';
			}
		}

		if ( ! $parts ) {
			$empty = Document::empty_page( $title );
			$empty->metadata['importedFrom'] = 'empty';
			return $empty;
		}

		// Merge all sources so header Elementor + body HTML appear together.
		$merged = array_shift( $parts );
		foreach ( $parts as $extra ) {
			$merged = $this->merge_documents( $merged, $extra );
		}
		// Drop empty decorative columns (common after Elementor structure import).
		$merged = $this->prune_empty_containers( $merged );
		$merged->metadata['importedFrom'] = implode( '+', $sources );
		$merged->title                    = $title;
		return $merged;
	}

	/**
	 * Append all root sections from $extra into $base.
	 */
	private function merge_documents( Document $base, Document $extra ): Document {
		$base_arr  = $base->to_array();
		$extra_arr = $extra->to_array();
		$root_id   = $base_arr['rootId'];
		$extra_root = $extra_arr['rootId'] ?? '';

		if ( $extra_root === '' || empty( $extra_arr['nodes'][ $extra_root ]['children'] ) ) {
			return $base;
		}

		foreach ( $extra_arr['nodes'][ $extra_root ]['children'] as $child_id ) {
			// Re-map all descendant ids to avoid collisions.
			$mapped = $this->clone_subtree( $extra_arr['nodes'], $child_id, $root_id );
			foreach ( $mapped['nodes'] as $nid => $node ) {
				$base_arr['nodes'][ $nid ] = $node;
			}
			$base_arr['nodes'][ $root_id ]['children'][] = $mapped['root'];
		}

		return new Document( $base_arr );
	}

	/**
	 * @param array<string, array<string, mixed>> $nodes
	 * @return array{root:string,nodes:array<string,array<string,mixed>>}
	 */
	private function clone_subtree( array $nodes, string $id, ?string $new_parent ): array {
		$src = $nodes[ $id ] ?? null;
		if ( ! $src ) {
			return array( 'root' => '', 'nodes' => array() );
		}
		$new_id = Node::generate_id();
		$out    = array();
		$copy   = $src;
		$copy['id']       = $new_id;
		$copy['parentId'] = $new_parent;
		$copy['children'] = array();
		$out[ $new_id ]   = $copy;

		foreach ( $src['children'] ?? array() as $cid ) {
			if ( ! is_string( $cid ) ) {
				continue;
			}
			$sub = $this->clone_subtree( $nodes, $cid, $new_id );
			foreach ( $sub['nodes'] as $nid => $n ) {
				$out[ $nid ] = $n;
			}
			if ( $sub['root'] !== '' ) {
				$out[ $new_id ]['children'][] = $sub['root'];
			}
		}

		return array( 'root' => $new_id, 'nodes' => $out );
	}

	/**
	 * Remove empty containers (and empty sections) without leaf content.
	 */
	private function prune_empty_containers( Document $doc ): Document {
		$arr   = $doc->to_array();
		$nodes = $arr['nodes'];
		$changed = true;
		while ( $changed ) {
			$changed = false;
			foreach ( $nodes as $id => $node ) {
				if ( ! is_array( $node ) ) {
					continue;
				}
				$type = $node['type'] ?? '';
				if ( ! in_array( $type, array( 'container', 'section' ), true ) ) {
					continue;
				}
				$children = $node['children'] ?? array();
				// Filter missing.
				$children = array_values(
					array_filter(
						$children,
						static fn( $cid ) => is_string( $cid ) && isset( $nodes[ $cid ] )
					)
				);
				if ( $children !== ( $node['children'] ?? array() ) ) {
					$nodes[ $id ]['children'] = $children;
					$changed = true;
				}
				if ( empty( $children ) && $type === 'container' ) {
					// Remove empty container from parent.
					$parent = $node['parentId'] ?? null;
					if ( $parent && isset( $nodes[ $parent ] ) ) {
						$nodes[ $parent ]['children'] = array_values(
							array_filter(
								$nodes[ $parent ]['children'] ?? array(),
								static fn( $cid ) => $cid !== $id
							)
						);
					}
					unset( $nodes[ $id ] );
					$changed = true;
				}
			}
		}
		// Remove empty sections.
		foreach ( $nodes as $id => $node ) {
			if ( ( $node['type'] ?? '' ) === 'section' && empty( $node['children'] ) ) {
				$parent = $node['parentId'] ?? null;
				if ( $parent && isset( $nodes[ $parent ] ) ) {
					$nodes[ $parent ]['children'] = array_values(
						array_filter(
							$nodes[ $parent ]['children'] ?? array(),
							static fn( $cid ) => $cid !== $id
						)
					);
				}
				unset( $nodes[ $id ] );
			}
		}
		$arr['nodes'] = $nodes;
		return new Document( $arr );
	}

	/**
	 * @param list<array<string, mixed>> $elements
	 */
	public function from_elementor( array $elements, string $title = '' ): Document {
		$root_id = Node::generate_id();
		$nodes   = array(
			$root_id => array(
				'id'       => $root_id,
				'type'     => 'root',
				'children' => array(),
				'settings' => array(),
			),
		);

		foreach ( $elements as $section ) {
			if ( ! is_array( $section ) || ( $section['elType'] ?? '' ) !== 'section' ) {
				continue;
			}
			$sec_id = Node::generate_id();
			$nodes[ $sec_id ] = array(
				'id'       => $sec_id,
				'type'     => 'section',
				'parentId' => $root_id,
				'children' => array(),
				'settings' => array( 'layout' => 'full' ),
				'styles'   => $this->elementor_section_styles( $section['settings'] ?? array() ),
			);
			$nodes[ $root_id ]['children'][] = $sec_id;

			$cols = isset( $section['elements'] ) && is_array( $section['elements'] ) ? $section['elements'] : array();
			// If multi-column, wrap in a row container.
			$row_id = null;
			if ( count( $cols ) > 1 ) {
				$row_id = Node::generate_id();
				$nodes[ $row_id ] = array(
					'id'       => $row_id,
					'type'     => 'container',
					'parentId' => $sec_id,
					'children' => array(),
					'settings' => array(
						'display'   => 'flex',
						'direction' => 'row',
						'gap'       => '12px',
						'width'     => '100%',
					),
				);
				$nodes[ $sec_id ]['children'][] = $row_id;
			}

			foreach ( $cols as $col ) {
				if ( ! is_array( $col ) || ( $col['elType'] ?? '' ) !== 'column' ) {
					continue;
				}
				$size = isset( $col['settings']['_column_size'] ) ? (int) $col['settings']['_column_size'] : 100;
				if ( $size < 5 ) {
					$size = 100;
				}
				$col_id = Node::generate_id();
				$parent = $row_id ? $row_id : $sec_id;
				$nodes[ $col_id ] = array(
					'id'       => $col_id,
					'type'     => 'container',
					'parentId' => $parent,
					'children' => array(),
					'settings' => array(
						'display'   => 'flex',
						'direction' => 'column',
						'gap'       => '12px',
						'width'     => $size . '%',
					),
					'styles'   => array(
						'desktop' => array(
							'width' => $size . '%',
						),
					),
				);
				$nodes[ $parent ]['children'][] = $col_id;

				$widgets = isset( $col['elements'] ) && is_array( $col['elements'] ) ? $col['elements'] : array();
				foreach ( $widgets as $w ) {
					if ( ! is_array( $w ) ) {
						continue;
					}
					$mapped = $this->map_elementor_widget( $w, $col_id );
					if ( ! $mapped ) {
						continue;
					}
					$nodes[ $mapped['id'] ] = $mapped;
					$nodes[ $col_id ]['children'][] = $mapped['id'];
				}
			}
		}

		if ( empty( $nodes[ $root_id ]['children'] ) ) {
			return Document::empty_page( $title );
		}

		return new Document(
			array(
				'type'    => 'page',
				'title'   => $title,
				'version' => '1.0',
				'status'  => 'draft',
				'rootId'  => $root_id,
				'nodes'   => $nodes,
				'metadata'=> array( 'importedFrom' => 'elementor' ),
			)
		);
	}

	/**
	 * @param array<string, mixed> $legacy
	 */
	public function from_legacy_aura( array $legacy, string $title = '' ): Document {
		$root_id = Node::generate_id();
		$nodes   = array(
			$root_id => array(
				'id'       => $root_id,
				'type'     => 'root',
				'children' => array(),
				'settings' => array(),
			),
		);

		$sections = $legacy['sections'] ?? array();
		if ( ! is_array( $sections ) ) {
			return Document::empty_page( $title );
		}

		foreach ( $sections as $section ) {
			if ( ! is_array( $section ) ) {
				continue;
			}
			$sec_id = Node::generate_id();
			$nodes[ $sec_id ] = array(
				'id'       => $sec_id,
				'type'     => 'section',
				'parentId' => $root_id,
				'children' => array(),
				'settings' => array( 'layout' => 'full' ),
			);
			$nodes[ $root_id ]['children'][] = $sec_id;

			$columns = $section['columns'] ?? array();
			if ( ! is_array( $columns ) ) {
				continue;
			}
			$row_id = null;
			if ( count( $columns ) > 1 ) {
				$row_id = Node::generate_id();
				$nodes[ $row_id ] = array(
					'id'       => $row_id,
					'type'     => 'container',
					'parentId' => $sec_id,
					'children' => array(),
					'settings' => array(
						'display'   => 'flex',
						'direction' => 'row',
						'gap'       => '12px',
						'width'     => '100%',
					),
				);
				$nodes[ $sec_id ]['children'][] = $row_id;
			}

			foreach ( $columns as $col ) {
				if ( ! is_array( $col ) ) {
					continue;
				}
				$w      = isset( $col['width'] ) ? (int) $col['width'] : 100;
				$col_id = Node::generate_id();
				$parent = $row_id ?: $sec_id;
				$nodes[ $col_id ] = array(
					'id'       => $col_id,
					'type'     => 'container',
					'parentId' => $parent,
					'children' => array(),
					'settings' => array(
						'display'   => 'flex',
						'direction' => 'column',
						'gap'       => '12px',
						'width'     => $w . '%',
					),
					'styles'   => array( 'desktop' => array( 'width' => $w . '%' ) ),
				);
				$nodes[ $parent ]['children'][] = $col_id;

				$widgets = $col['widgets'] ?? array();
				if ( ! is_array( $widgets ) ) {
					continue;
				}
				foreach ( $widgets as $widget ) {
					if ( ! is_array( $widget ) ) {
						continue;
					}
					$type = sanitize_key( (string) ( $widget['type'] ?? '' ) );
					if ( $type === '' ) {
						continue;
					}
					$wid = Node::generate_id();
					$settings = isset( $widget['settings'] ) && is_array( $widget['settings'] ) ? $widget['settings'] : array();
					$styles   = array();
					if ( isset( $widget['style'] ) && is_array( $widget['style'] ) ) {
						$styles['desktop'] = $this->flat_style_map( $widget['style'] );
					}
					$nodes[ $wid ] = array(
						'id'       => $wid,
						'type'     => $type,
						'parentId' => $col_id,
						'children' => array(),
						'settings' => $settings,
						'styles'   => $styles,
					);
					$nodes[ $col_id ]['children'][] = $wid;
				}
			}
		}

		return new Document(
			array(
				'type'     => 'page',
				'title'    => $title,
				'version'  => '1.0',
				'rootId'   => $root_id,
				'nodes'    => $nodes,
				'metadata' => array( 'importedFrom' => 'aura_legacy' ),
			)
		);
	}

	public function from_html( string $html, string $title = '' ): Document {
		$root_id = Node::generate_id();
		$sec_id  = Node::generate_id();
		$col_id  = Node::generate_id();
		$nodes   = array(
			$root_id => array(
				'id'       => $root_id,
				'type'     => 'root',
				'children' => array( $sec_id ),
				'settings' => array(),
			),
			$sec_id  => array(
				'id'       => $sec_id,
				'type'     => 'section',
				'parentId' => $root_id,
				'children' => array( $col_id ),
				'settings' => array( 'layout' => 'boxed' ),
			),
			$col_id  => array(
				'id'       => $col_id,
				'type'     => 'container',
				'parentId' => $sec_id,
				'children' => array(),
				'settings' => array(
					'display'   => 'flex',
					'direction' => 'column',
					'gap'       => '16px',
					'width'     => '100%',
				),
			),
		);

		$libxml = libxml_use_internal_errors( true );
		$dom    = new \DOMDocument();
		$wrapped = '<?xml encoding="utf-8" ?><div id="ab-import-root">' . $html . '</div>';
		$dom->loadHTML( $wrapped, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
		libxml_clear_errors();
		libxml_use_internal_errors( $libxml );

		$root = $dom->getElementById( 'ab-import-root' );
		if ( $root ) {
			$this->walk_html( $root, $col_id, $nodes, true );
		}

		// Deduplicate consecutive identical headings.
		$nodes[ $col_id ]['children'] = $this->dedupe_children( $nodes, $nodes[ $col_id ]['children'] );

		if ( empty( $nodes[ $col_id ]['children'] ) ) {
			// Fallback: single HTML block (never leave stripped <style> as visible text).
			$clean = \AuraBuilder\Support\Assets::sanitize_user_html( $html, false );
			if ( $clean !== '' ) {
				$hid = Node::generate_id();
				$nodes[ $hid ] = array(
					'id'       => $hid,
					'type'     => 'html',
					'parentId' => $col_id,
					'children' => array(),
					'settings' => array( 'code' => $clean ),
				);
				$nodes[ $col_id ]['children'][] = $hid;
			}
		}

		return new Document(
			array(
				'type'     => 'page',
				'title'    => $title,
				'version'  => '1.0',
				'rootId'   => $root_id,
				'nodes'    => $nodes,
				'metadata' => array( 'importedFrom' => 'post_content' ),
			)
		);
	}

	/**
	 * @param array<string, array<string, mixed>> $nodes
	 * @param list<string> $children
	 * @return list<string>
	 */
	private function dedupe_children( array $nodes, array $children ): array {
		$out  = array();
		$last = null;
		foreach ( $children as $id ) {
			$n = $nodes[ $id ] ?? null;
			if ( ! $n ) {
				continue;
			}
			$key = $n['type'] . '|' . ( $n['settings']['text'] ?? $n['settings']['code'] ?? $n['settings']['url'] ?? '' );
			if ( $last === $key && in_array( $n['type'], array( 'heading', 'divider' ), true ) ) {
				continue;
			}
			$last  = $key;
			$out[] = $id;
		}
		return $out;
	}

	/**
	 * @param array<string, array<string, mixed>> $nodes
	 */
	private function walk_html( \DOMNode $node, string $parent_id, array &$nodes, bool $is_root = false ): void {
		if ( $node instanceof \DOMText ) {
			$text = trim( preg_replace( '/\s+/', ' ', $node->textContent ) ?? '' );
			if ( $text === '' || $is_root ) {
				return;
			}
			// Only if parent isn't already collecting via element.
			return;
		}

		if ( ! $node instanceof \DOMElement ) {
			foreach ( $node->childNodes ?? array() as $child ) {
				$this->walk_html( $child, $parent_id, $nodes );
			}
			return;
		}

		$tag = strtolower( $node->tagName );

		// Skip script/style.
		if ( in_array( $tag, array( 'script', 'style', 'noscript' ), true ) ) {
			return;
		}

		if ( in_array( $tag, array( 'h1', 'h2', 'h3', 'h4' ), true ) ) {
			$text = trim( $node->textContent );
			if ( $text !== '' ) {
				$id = Node::generate_id();
				$nodes[ $id ] = array(
					'id'       => $id,
					'type'     => 'heading',
					'parentId' => $parent_id,
					'children' => array(),
					'settings' => array(
						'text'  => $text,
						'tag'   => $tag,
						'align' => $this->detect_align( $node ),
					),
				);
				$nodes[ $parent_id ]['children'][] = $id;
			}
			return;
		}

		if ( $tag === 'p' ) {
			$html = $this->inner_html( $node );
			$text = trim( $node->textContent );
			if ( $text !== '' ) {
				$id = Node::generate_id();
				$nodes[ $id ] = array(
					'id'       => $id,
					'type'     => 'text',
					'parentId' => $parent_id,
					'children' => array(),
					'settings' => array(
						'text'  => $html,
						'align' => $this->detect_align( $node ),
					),
				);
				$nodes[ $parent_id ]['children'][] = $id;
			}
			return;
		}

		if ( $tag === 'img' ) {
			$src = $node->getAttribute( 'src' );
			if ( $src !== '' ) {
				$id = Node::generate_id();
				$nodes[ $id ] = array(
					'id'       => $id,
					'type'     => 'image',
					'parentId' => $parent_id,
					'children' => array(),
					'settings' => array(
						'url'   => $src,
						'alt'   => $node->getAttribute( 'alt' ),
						'align' => 'center',
					),
				);
				$nodes[ $parent_id ]['children'][] = $id;
			}
			return;
		}

		if ( $tag === 'hr' ) {
			$id = Node::generate_id();
			$nodes[ $id ] = array(
				'id'       => $id,
				'type'     => 'divider',
				'parentId' => $parent_id,
				'children' => array(),
				'settings' => array(),
			);
			$nodes[ $parent_id ]['children'][] = $id;
			return;
		}

		if ( $tag === 'a' && $node->getAttribute( 'class' ) && str_contains( $node->getAttribute( 'class' ), 'button' ) ) {
			$id = Node::generate_id();
			$nodes[ $id ] = array(
				'id'       => $id,
				'type'     => 'button',
				'parentId' => $parent_id,
				'children' => array(),
				'settings' => array(
					'text'  => trim( $node->textContent ) ?: 'Botão',
					'url'   => $node->getAttribute( 'href' ) ?: '#',
					'align' => 'left',
				),
			);
			$nodes[ $parent_id ]['children'][] = $id;
			return;
		}

		// Cover / figure: pull image.
		if ( $tag === 'figure' || ( $tag === 'div' && str_contains( $node->getAttribute( 'class' ), 'wp-block-cover' ) ) ) {
			$imgs = $node->getElementsByTagName( 'img' );
			if ( $imgs->length > 0 ) {
				$img = $imgs->item( 0 );
				if ( $img instanceof \DOMElement ) {
					$src = $img->getAttribute( 'src' );
					if ( $src !== '' ) {
						$id = Node::generate_id();
						$nodes[ $id ] = array(
							'id'       => $id,
							'type'     => 'image',
							'parentId' => $parent_id,
							'children' => array(),
							'settings' => array(
								'url'   => $src,
								'alt'   => $img->getAttribute( 'alt' ),
								'align' => 'center',
							),
						);
						$nodes[ $parent_id ]['children'][] = $id;
					}
				}
			}
			// Continue into children for text.
		}

		// Spacer.
		if ( $tag === 'div' && str_contains( $node->getAttribute( 'class' ), 'spacer' ) ) {
			$h  = 32;
			$st = $node->getAttribute( 'style' );
			if ( preg_match( '/height:\s*(\d+)/', $st, $m ) ) {
				$h = (int) $m[1];
			}
			$id = Node::generate_id();
			$nodes[ $id ] = array(
				'id'       => $id,
				'type'     => 'spacer',
				'parentId' => $parent_id,
				'children' => array(),
				'settings' => array( 'height' => $h ),
			);
			$nodes[ $parent_id ]['children'][] = $id;
			return;
		}

		foreach ( iterator_to_array( $node->childNodes ) as $child ) {
			$this->walk_html( $child, $parent_id, $nodes );
		}
	}

	/**
	 * @param array<string, mixed> $w
	 * @return array<string, mixed>|null
	 */
	private function map_elementor_widget( array $w, string $parent_id ): ?array {
		$el_type = (string) ( $w['elType'] ?? '' );
		if ( $el_type === 'section' || $el_type === 'column' ) {
			// Nested sections rare — skip deep for MVP.
			return null;
		}
		$type = sanitize_key( (string) ( $w['widgetType'] ?? '' ) );
		$s    = isset( $w['settings'] ) && is_array( $w['settings'] ) ? $w['settings'] : array();
		$id   = Node::generate_id();

		switch ( $type ) {
			case 'heading':
				return array(
					'id'       => $id,
					'type'     => 'heading',
					'parentId' => $parent_id,
					'children' => array(),
					'settings' => array(
						'text'  => (string) ( $s['title'] ?? $s['text'] ?? 'Título' ),
						'tag'   => $this->map_heading_tag( $s['header_size'] ?? 'h2' ),
						'align' => $this->map_align( $s['align'] ?? 'left' ),
					),
				);
			case 'text-editor':
			case 'text':
				return array(
					'id'       => $id,
					'type'     => 'text',
					'parentId' => $parent_id,
					'children' => array(),
					'settings' => array(
						'text'  => (string) ( $s['editor'] ?? $s['text'] ?? '' ),
						'align' => $this->map_align( $s['align'] ?? 'left' ),
					),
				);
			case 'image':
				$url = '';
				if ( isset( $s['image']['url'] ) ) {
					$url = (string) $s['image']['url'];
				} elseif ( isset( $s['image']['id'] ) ) {
					$url = (string) ( wp_get_attachment_url( (int) $s['image']['id'] ) ?: '' );
				}
				return array(
					'id'       => $id,
					'type'     => 'image',
					'parentId' => $parent_id,
					'children' => array(),
					'settings' => array(
						'url'   => $url,
						'alt'   => (string) ( $s['image']['alt'] ?? '' ),
						'align' => $this->map_align( $s['align'] ?? 'center' ),
					),
				);
			case 'button':
				$url = '#';
				if ( isset( $s['link']['url'] ) ) {
					$url = (string) $s['link']['url'];
				}
				return array(
					'id'       => $id,
					'type'     => 'button',
					'parentId' => $parent_id,
					'children' => array(),
					'settings' => array(
						'text'  => (string) ( $s['text'] ?? 'Botão' ),
						'url'   => $url,
						'align' => $this->map_align( $s['align'] ?? 'center' ),
					),
					'styles'   => array(
						'desktop' => array_filter(
							array(
								'background' => isset( $s['background_color'] ) ? (string) $s['background_color'] : null,
							)
						),
					),
				);
			case 'spacer':
				$h = 40;
				if ( isset( $s['space']['size'] ) ) {
					$h = (int) $s['space']['size'];
				}
				return array(
					'id'       => $id,
					'type'     => 'spacer',
					'parentId' => $parent_id,
					'children' => array(),
					'settings' => array( 'height' => max( 8, $h ) ),
				);
			case 'divider':
				return array(
					'id'       => $id,
					'type'     => 'divider',
					'parentId' => $parent_id,
					'children' => array(),
					'settings' => array(),
				);
			default:
				// Best-effort dump.
				$text = (string) ( $s['editor'] ?? $s['title'] ?? $s['text'] ?? '' );
				if ( $text === '' && isset( $s['image']['url'] ) ) {
					return array(
						'id'       => $id,
						'type'     => 'image',
						'parentId' => $parent_id,
						'children' => array(),
						'settings' => array(
							'url'   => (string) $s['image']['url'],
							'alt'   => '',
							'align' => 'center',
						),
					);
				}
				if ( $text === '' ) {
					return null;
				}
				return array(
					'id'       => $id,
					'type'     => 'html',
					'parentId' => $parent_id,
					'children' => array(),
					'settings' => array( 'code' => $text ),
				);
		}
	}

	/**
	 * @param array<string, mixed> $settings
	 * @return array{desktop?:array<string,mixed>}
	 */
	private function elementor_section_styles( array $settings ): array {
		$desktop = array();
		if ( ! empty( $settings['background_color'] ) ) {
			$desktop['background'] = (string) $settings['background_color'];
		}
		return $desktop ? array( 'desktop' => $desktop ) : array();
	}

	/**
	 * @param array<string, mixed> $style
	 * @return array<string, mixed>
	 */
	private function flat_style_map( array $style ): array {
		$map = array(
			'width'         => 'width',
			'minHeight'     => 'min_height',
			'paddingTop'    => 'padding',
			'marginTop'     => 'margin',
			'opacity'       => 'opacity',
			'fontSize'      => 'font_size',
			'fontWeight'    => 'font_weight',
			'borderRadius'  => 'radius',
		);
		$out = array();
		foreach ( $style as $k => $v ) {
			if ( $v === '' || $v === null ) {
				continue;
			}
			$key = $map[ $k ] ?? sanitize_key( (string) $k );
			if ( is_scalar( $v ) ) {
				$out[ $key ] = $v;
			}
		}
		return $out;
	}

	private function map_heading_tag( mixed $size ): string {
		$size = strtolower( (string) $size );
		if ( in_array( $size, array( 'h1', 'h2', 'h3', 'h4' ), true ) ) {
			return $size;
		}
		return 'h2';
	}

	private function map_align( mixed $a ): string {
		$a = strtolower( (string) $a );
		return in_array( $a, array( 'left', 'center', 'right' ), true ) ? $a : 'left';
	}

	private function detect_align( \DOMElement $el ): string {
		$style = $el->getAttribute( 'style' );
		if ( preg_match( '/text-align\s*:\s*(left|center|right)/i', $style, $m ) ) {
			return strtolower( $m[1] );
		}
		$class = $el->getAttribute( 'class' );
		if ( str_contains( $class, 'has-text-align-center' ) || str_contains( $class, 'aligncenter' ) ) {
			return 'center';
		}
		if ( str_contains( $class, 'has-text-align-right' ) ) {
			return 'right';
		}
		return 'left';
	}

	private function inner_html( \DOMElement $el ): string {
		$html = '';
		foreach ( $el->childNodes as $child ) {
			$html .= $el->ownerDocument ? $el->ownerDocument->saveHTML( $child ) : '';
		}
		return $html;
	}

	private function has_leaf_widgets( Document $doc ): bool {
		foreach ( $doc->nodes as $node ) {
			if ( ! in_array( $node->type, array( 'root', 'section', 'container' ), true ) ) {
				return true;
			}
		}
		return false;
	}
}
