<?php
/**
 * Vertical menu module.
 *
 * @package MenuVerse
 */

namespace WPXero\MenuVerse\Module\Vertical_Menu;

use WPXero\MenuVerse\Frontend\Renderer;
use WPXero\MenuVerse\Menus\Menu_Tree;
use WPXero\MenuVerse\Modules\Abstract_Module;
use WPXero\MenuVerse\Modules\Menu_Backed;

defined( 'ABSPATH' ) || exit;

/**
 * Sidebar style navigation with collapsible groups.
 */
final class Module extends Abstract_Module {

	use Menu_Backed;

	/**
	 * Shortcode tag.
	 *
	 * @var string
	 */
	const SHORTCODE = 'menuverse_vertical_menu';

	/**
	 * Block name.
	 *
	 * @var string
	 */
	const BLOCK = 'menuverse/vertical-menu';


	/**
	 * Returns the module identifier.
	 *
	 * @return string
	 */
	public static function id() {
		return 'vertical_menu';
	}

	/**
	 * Returns the translated module label.
	 *
	 * @return string
	 */
	public static function label() {
		return __( 'Vertical Menu', 'menuverse' );
	}

	/**
	 * Returns the translated module description.
	 *
	 * @return string
	 */
	public static function description() {
		return __( 'A menu down the side of the page, with sections that open and close.', 'menuverse' );
	}

	/**
	 * Returns the settings schema.
	 *
	 * @return array
	 */
	public static function schema() {
		return require __DIR__ . '/schema.php';
	}

	/**
	 * Registers hooks. Runs only when the module is enabled.
	 *
	 * @return void
	 */
	public function boot() {
		add_shortcode( self::SHORTCODE, array( $this, 'render_shortcode' ) );
		add_action( 'init', array( $this, 'register_style' ), 4 );
		add_action( 'init', array( $this, 'register_block' ) );
		add_action( 'wp', array( $this, 'resolve_render_paths' ) );
	}

	/**
	 * Registers the stylesheet shared by the frontend and the editor.
	 *
	 * @return void
	 */
	public function register_style() {
		$this->register_module_assets( true, false );
	}

	/**
	 * Registers the block type.
	 *
	 * @return void
	 */
	public function register_block() {
		if ( ! function_exists( 'register_block_type' ) ) {
			return;
		}

		register_block_type(
			__DIR__ . '/block.json',
			array(
				'render_callback' => array( $this, 'render_block' ),
			)
		);
	}

	/**
	 * Returns the priority the automatic placement renders at.
	 *
	 * Keeps a stable order against the other modules that also place
	 * themselves on `wp_body_open`.
	 *
	 * @return int
	 */
	protected function auto_insert_priority() {
		return 20;
	}

	/**
	 * Registers the module's frontend script.
	 *
	 * @return void
	 */
	public function register_assets() {
		$this->register_module_assets();
	}

	/**
	 * Renders the module for the shortcode.
	 *
	 * @param array|string $atts Shortcode attributes.
	 * @return string
	 */
	public function render_shortcode( $atts ) {
		$atts = shortcode_atts(
			array(
				'menu'   => '',
				'mode'   => '',
				'sticky' => '',
				'title'  => '',
			),
			is_array( $atts ) ? $atts : array(),
			self::SHORTCODE
		);

		$args = array();

		if ( '' !== $atts['menu'] ) {
			$args['menu'] = sanitize_text_field( $atts['menu'] );
		}

		if ( '' !== $atts['mode'] ) {
			$args['submenu_mode'] = sanitize_key( $atts['mode'] );
		}

		if ( '' !== $atts['sticky'] ) {
			$args['sticky'] = rest_sanitize_boolean( $atts['sticky'] );
		}

		if ( '' !== $atts['title'] ) {
			$args['title'] = sanitize_text_field( $atts['title'] );
		}

		return $this->output( $this->clean( $args ), array( 'source' => 'shortcode' ) );
	}

	/**
	 * Renders the module for the block.
	 *
	 * @param array $attributes Block attributes.
	 * @return string
	 */
	public function render_block( $attributes ) {
		$args = array();

		foreach ( array( 'menu', 'submenu_mode', 'title' ) as $key ) {
			if ( isset( $attributes[ $key ] ) && '' !== $attributes[ $key ] ) {
				$args[ $key ] = $attributes[ $key ];
			}
		}

		if ( isset( $attributes['sticky'] ) ) {
			$args['sticky'] = (bool) $attributes['sticky'];
		}

		return $this->output( $this->clean( $args ), array( 'source' => 'block' ) );
	}

	/**
	 * Renders the module markup.
	 *
	 * @param array $settings Resolved settings.
	 * @param array $context  Render context.
	 * @return string
	 */
	public function render( array $settings, array $context ) {
		$tree = $this->resolve_menu( $settings );

		if ( ! $tree instanceof Menu_Tree || ! $tree->items() ) {
			return '';
		}

		$uid      = Renderer::next_id();
		$root_id  = 'menuverse-vertical-' . $uid;
		$title_id = $root_id . '-title';
		$title    = trim( (string) $settings['title'] );
		$mode     = $settings['submenu_mode'];

		$classes = array(
			'menuverse-vertical',
			'menuverse-vertical--' . sanitize_html_class( $mode ),
			'menuverse-vertical--marker-' . sanitize_html_class( $settings['active_indicator'] ),
		);

		if ( ! empty( $settings['sticky'] ) ) {
			$classes[] = 'menuverse-vertical--sticky';
		}

		if ( ! empty( $settings['dividers'] ) ) {
			$classes[] = 'menuverse-vertical--dividers';
		}

		$list = Renderer::render_tree(
			$tree,
			array(
				'base_class'      => 'menuverse-vertical',
				'id_prefix'       => $root_id,
				'submenu_toggles' => 'expanded' !== $mode,
				'toggle_attrs'    => 'data-mv-toggle',
				'expand_current'  => ! empty( $settings['expand_current'] ) && 'flyout' !== $mode,
			)
		);

		$label = '' !== $title
			? sprintf( ' aria-labelledby="%s"', esc_attr( $title_id ) )
			: sprintf( ' aria-label="%s"', esc_attr__( 'Sections', 'menuverse' ) );

		$html = sprintf(
			'<nav id="%1$s" class="%2$s" style="%3$s" data-menuverse-vertical data-mv-mode="%4$s" data-mv-single="%5$s"%6$s>',
			esc_attr( $root_id ),
			esc_attr( implode( ' ', $classes ) ),
			esc_attr( $this->style_tokens( $settings ) ),
			esc_attr( $mode ),
			! empty( $settings['single_open'] ) ? '1' : '0',
			// Built from esc_attr above.
			$label // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		);

		if ( '' !== $title ) {
			$html .= sprintf(
				'<p class="menuverse-vertical__title" id="%s">%s</p>',
				esc_attr( $title_id ),
				esc_html( $title )
			);
		}

		return $html . $list . '</nav>';
	}
}
