<?php
/**
 * Block navigation adapter.
 *
 * @package MenuVerse
 */

namespace WPXero\MenuVerse\Menus;

defined( 'ABSPATH' ) || exit;

/**
 * Adapts the `wp_navigation` post type used by the block Navigation menu.
 */
final class Block_Menu_Source implements Menu_Source {

	/**
	 * Block names treated as navigation entries.
	 *
	 * @var string[]
	 */
	const LINK_BLOCKS = array(
		'core/navigation-link',
		'core/navigation-submenu',
		'core/home-link',
	);

	/**
	 * Returns the source identifier.
	 *
	 * @return string
	 */
	public function id() {
		return 'block';
	}

	/**
	 * Returns the translated source label.
	 *
	 * @return string
	 */
	public function label() {
		return __( 'Block navigation menus', 'menuverse' );
	}

	/**
	 * Lists block navigation menus.
	 *
	 * @return array[]
	 */
	public function list_menus() {
		if ( ! post_type_exists( 'wp_navigation' ) ) {
			return array();
		}

		$posts = get_posts(
			array(
				'post_type'              => 'wp_navigation',
				'post_status'            => array( 'publish', 'draft' ),
				'posts_per_page'         => 100,
				'orderby'                => 'title',
				'order'                  => 'ASC',
				'no_found_rows'          => true,
				'update_post_term_cache' => false,
			)
		);

		$results = array();

		foreach ( $posts as $post ) {
			$results[] = array(
				'source'    => $this->id(),
				'id'        => (int) $post->ID,
				'name'      => '' !== $post->post_title ? $post->post_title : __( '(untitled menu)', 'menuverse' ),
				'count'     => count( $this->parse( $post->post_content ) ),
				'locations' => array(),
			);
		}

		return $results;
	}

	/**
	 * Determines whether a block navigation menu exists.
	 *
	 * @param int|string $menu_id Post ID.
	 * @return bool
	 */
	public function exists( $menu_id ) {
		$post = get_post( (int) $menu_id );

		return $post instanceof \WP_Post && 'wp_navigation' === $post->post_type;
	}

	/**
	 * Builds the normalized tree for a block navigation menu.
	 *
	 * @param int|string $menu_id Post ID.
	 * @return Menu_Tree|null
	 */
	public function get_tree( $menu_id ) {
		$post = get_post( (int) $menu_id );

		if ( ! $post instanceof \WP_Post || 'wp_navigation' !== $post->post_type ) {
			return null;
		}

		$items = $this->to_items( parse_blocks( $post->post_content ) );

		return new Menu_Tree( $this->id(), (int) $post->ID, $post->post_title, $items );
	}

	/**
	 * Flattens parsed blocks to link blocks only, for counting.
	 *
	 * @param string $content Post content.
	 * @return array[]
	 */
	private function parse( $content ) {
		$blocks = parse_blocks( $content );
		$links  = array();

		foreach ( $blocks as $block ) {
			if ( in_array( isset( $block['blockName'] ) ? $block['blockName'] : '', self::LINK_BLOCKS, true ) ) {
				$links[] = $block;
			}
		}

		return $links;
	}

	/**
	 * Converts parsed blocks to normalized items.
	 *
	 * @param array[] $blocks Parsed blocks.
	 * @return array[]
	 */
	private function to_items( array $blocks ) {
		$items = array();

		foreach ( $blocks as $index => $block ) {
			$name = isset( $block['blockName'] ) ? $block['blockName'] : '';

			if ( ! in_array( $name, self::LINK_BLOCKS, true ) ) {
				// Layout blocks such as core/group may wrap links.
				if ( ! empty( $block['innerBlocks'] ) ) {
					$items = array_merge( $items, $this->to_items( $block['innerBlocks'] ) );
				}

				continue;
			}

			$attrs = isset( $block['attrs'] ) && is_array( $block['attrs'] ) ? $block['attrs'] : array();

			if ( 'core/home-link' === $name ) {
				$title = isset( $attrs['label'] ) ? (string) $attrs['label'] : __( 'Home', 'menuverse' );
				$url   = home_url( '/' );
			} else {
				$title = isset( $attrs['label'] ) ? (string) $attrs['label'] : '';
				$url   = isset( $attrs['url'] ) ? (string) $attrs['url'] : '';
			}

			$items[] = array_merge(
				Menu_Tree::item_defaults(),
				array(
					'id'       => isset( $attrs['id'] ) ? (int) $attrs['id'] : ( $index + 1 ),
					'title'    => $title,
					'url'      => $url,
					'target'   => ! empty( $attrs['opensInNewTab'] ) ? '_blank' : '',
					'rel'      => isset( $attrs['rel'] ) ? (string) $attrs['rel'] : '',
					'classes'  => isset( $attrs['className'] ) ? array_values( array_filter( explode( ' ', (string) $attrs['className'] ) ) ) : array(),
					'extras'   => Item_Extras::normalize( isset( $attrs['menuverse'] ) ? $attrs['menuverse'] : array() ),
					'children' => ! empty( $block['innerBlocks'] ) ? $this->to_items( $block['innerBlocks'] ) : array(),
				)
			);
		}

		return $items;
	}
}
