<?php
/**
 * WeCodeArt Framework
 *
 * WARNING: This file is part of the core WeCodeArt Framework. DO NOT edit this file under any circumstances.
 * Please do all modifications in the form of a child theme.
 *
 * @package		WeCodeArt Framework
 * @subpackage  Gutenberg\Blocks
 * @copyright   Copyright (c) 2025, WeCodeArt Framework
 * @since		6.0.0
 * @version		6.7.4
 */

namespace WeCodeArt\Gutenberg\Blocks\Design;

defined( 'ABSPATH' ) || exit;

use WeCodeArt\Singleton;
use WeCodeArt\Gutenberg\Blocks\Dynamic;
use function WeCodeArt\Functions\{ get_prop, array_extract, toJSON };

/**
 * Gutenberg Group blocks.
 */
class Group extends Dynamic {

	use Singleton;

	/**
	 * Block namespace.
	 *
	 * @var string
	 */
	protected $namespace = 'core';

	/**
	 * Block name.
	 *
	 * @var string
	 */
	protected $block_name = 'group';

	/**
	 * Block args.
	 *
	 * @param	array $current	Existing register args
	 *
	 * @return 	array
	 */
	public function block_type_args( $current ): array {
		$attributes	= get_prop( $current, [ 'attributes' ], [] );
		$supports 	= get_prop( $current, [ 'supports' ], [] );

		return [
			'render_callback' 	=> [ $this, 'render' ],
			'supports'			=> wp_parse_args( [
				'shadow'		=> true, 
			], $supports ),
			'variation_callback' => fn() => [
				[
					'name'        => 'core/group/marquee',
					'title'       => esc_html__( 'Group: Marquee', 'wecodeart' ),
					'description' => esc_html__( 'Gather blocks in a sliding container.', 'wecodeart' ),
					'icon'		  => 'align-right',
					'isDefault'   => false,
					'isActive'    => ['namespace'],
					'scope'       => [ 'inserter' ],
					'attributes'  => [
						'namespace'	=> 'wecodeart/group/marquee',
						'layout'	=> [
							'type' 				=> 'flex',
							'flexWrap' 			=> 'nowrap',
							'justifyContent' 	=> 'center',
							'alignItems' 		=> 'center'
						]
					],
				],
				[
					'name'        => 'core/group/collapse',
					'title'       => esc_html__( 'Group: Collapse', 'wecodeart' ),
					'description' => esc_html__( 'Gather blocks in a collapsible container.', 'wecodeart' ),
					'icon'		  => 'editor-expand',
					'isDefault'   => false,
					'isActive'    => ['namespace'],
					'scope'       => [ 'inserter' ],
					'attributes'  => [
						'namespace'	=> 'wecodeart/group/collapse',
					],
				],
				[
					'name'        => 'core/group/offcanvas',
					'title'       => esc_html__( 'Group: Offcanvas', 'wecodeart' ),
					'description' => esc_html__( 'Gather blocks in an offcanvas container.', 'wecodeart' ),
					'icon'		  => 'exit',
					'isDefault'   => false,
					'isActive'    => ['namespace'],
					'scope'       => [ 'inserter' ],
					'attributes'  => [
						'namespace'	=> 'wecodeart/group/offcanvas',
					],
				]
			],
			'attributes' 		=> wp_parse_args( [
				'namespace'		=> [
					'type'		=> 'string',
				],
			], $attributes )
		];
	}

	/**
	 * Dynamically renders the `core/group` block.
	 *
	 * @param 	string 	$attributes 	The block attrs.
	 * @param 	string 	$content 		The block markup.
	 *
	 * @return 	string 	The block markup.
	 */
	public function render( array $attributes = [], string $content = '' ): string {
		$content 	= wecodeart( 'dom' )::processor( $content );

		static $processed = null;
		// Handle <main /> tag (once).
		if( $content->next_tag( [ 'tag_name' => 'main' ] ) ) {
			if( is_null( $processed ) ) {
				$content->set_attribute( 'class', 'wp-site-main ' . $content->get_attribute( 'class' ) );
				$processed = true;
			}
		}

		// Handle marquee group.
		if( self::is_variation( $attributes, 'marquee' ) ) {
			$content = self::create_marquee( $attributes, $content );

			static $marquee_styles = null;
			if( is_null( $marquee_styles ) ) {
				$marquee_styles = \wp_add_inline_style( $this->get_asset_handle(), self::marquee_styles() );
			}
		}

		// Handle collapsible group.
		if( self::is_variation( $attributes, 'collapse' ) ) {
			$content = self::create_collapse( $attributes, $content );
		}

		// Handle offcanvas group.
		if( self::is_variation( $attributes, 'offcanvas' ) ) {
			$content = self::create_offcanvas( $attributes, $content );
		}

		// Add parallax support.
		if( str_contains( get_prop( $attributes, [ 'className' ], '' ), 'has-parallax' ) ) {
			wecodeart( 'styles' )->Components->load( [ 'parallax' ] );
		}
	
		return (string) $content;
	}

	/**
	 * Determines whether the block is a marquee block.
	 *
	 * @param 	array 	$attributes The block attrs.
	 *
	 * @return 	bool 	Whether the block is a marquee block.
	 */
	public static function is_variation( array $attributes = [], string $type = '' ): bool {
		if ( get_prop( $attributes, [ 'namespace' ], '' ) === 'wecodeart/group/' . $type ) {
			return true;
		}

		return false;
	}

	/**
	 * Generate marquee markup.
	 *
	 * @param 	array 	$attributes 	The block attrs.
	 * @param 	string 	$content 		The block markup.
	 *
	 * @return 	string 	The block updated markup.
	 */
	public static function create_marquee( array $attributes, string $content ): string {
		$dom 	= wecodeart( 'dom' )::create( $content );
		$div 	= wecodeart( 'dom' )::get_element( 'div', $dom );
		$wrap	= wecodeart( 'dom' )::create_element( 'div', $dom );
		$wrap->setAttribute( 'class', 'wp-block-group__marquee' );
		
		$styles 		= [];
		$orientation 	= get_prop( $attributes, [ 'layout', 'orientation' ] );
		$directionY 	= get_prop( $attributes, [ 'layout', 'verticalAlignment' ] );
		$directionX 	= get_prop( $attributes, [ 'layout', 'justifyContent' ] );

		// Handle direction
		if( ( $orientation === 'vertical' && $directionY === 'bottom' ) || ( $orientation === 'horizontal' && $directionX === 'right' ) ) {
			$styles[] = '--marquee-direction:reverse';
		}

		// Handle alternate
		if( ( $orientation === 'vertical' && $directionY === 'space-between' ) || ( $orientation === 'horizontal' && $directionX === 'space-between' ) ) {
			$styles[] = '--marquee-direction:alternate';
		}

		// Handle pause state
		if( ( $orientation === 'vertical' && $directionY === 'center' ) || ( $orientation === 'horizontal' && $directionX === 'center' ) ) {
			$styles[] = '--marquee-state:paused';
		}

		// Handle gap
		if( $blockGap = get_prop( $attributes, [ 'style', 'spacing', 'blockGap' ] ) ) {
			$styles[] = '--marquee-gap:' . wecodeart( 'styles' )::format_variable( $blockGap );
		}

		if( ! empty( $styles ) ) {
			$div->setAttribute( 'style', join( ';', $styles ) );
		}
		
		$count 	= $div->childNodes->count();
		for ( $i = 0; $i < $count; $i++ ) {
			$item = $div->childNodes->item( $i );

			if ( ! $item || ! method_exists( $item, 'setAttribute' ) ) {
				continue;
			}

			$wrap->appendChild( $item );
		}

		for ( $j = 0; $j < 2; $j++ ) {
			$clone = wecodeart( 'dom' )::element( $wrap->cloneNode( true ) );

			if ( ! $clone ) {
				continue;
			}

			$clone->setAttribute( 'aria-hidden', 'true' );
			wecodeart( 'dom' )::add_classes( $clone, [ 'is-cloned' ] );
			$div->appendChild( $clone );
		}

		$div->insertBefore( $wrap, $div->firstChild );
		wecodeart( 'dom' )::add_classes( $div, [ 'wp-block-group--marquee' ] );

		$content = $dom->saveHTML();

		return $content;
	}

	/**
	 * Generate collapse markup.
	 *
	 * @param 	array 	$attributes 	The block attrs.
	 * @param 	string 	$content 		The block markup.
	 *
	 * @return 	string 	The block updated markup.
	 */
	public static function create_collapse( array $attributes, string $content ): string {
		$tagName 	= get_prop( $attributes, [ 'tagName' ], 'div' );
		$className  = get_prop( $attributes, [ 'className' ], '' );
		$ariaLabel  = get_prop( $attributes, [ 'metadata', 'name' ] ) ?: __( 'Group', 'wecodeart' );
		$ariaLabel  = sprintf( esc_html__( 'Toggle %s', 'wecodeart' ), $ariaLabel );
		$itemID 	= wp_unique_id( 'wp-collapse-' );
		$isHidden 	= '';
		$isDisplay 	= '';
		$toggler 	= [
			'type' 	=> 'collapse',
			'label'	=> $ariaLabel,
			'id'	=> $itemID,
			'class'	=> [ 
				'wp-element-button',
				'has-' . get_prop( $attributes, [ 'backgroundColor' ], 'accent' ). '-background-color',
				'has-' . get_prop( $attributes, [ 'textColor' ], 'dark' ). '-color',
			],
		];

		// Regular expression to match the specific class pattern
		$pattern = '/\b(d-(sm|md|lg|xl|xxl)-(block|flex|grid|inline-block))\b/';
		if ( preg_match( $pattern, $className, $matches ) ) {
			$isHidden 	= preg_replace_callback( $pattern, fn( $matches ) => 'd-' . $matches[2] . '-none', $matches[1] );
		} else {
			$isHidden 	= 'd-lg-none';
			$isDisplay 	= 'd-lg-block';
		}

		wecodeart( 'styles' )->Utilities->load( [ $isHidden ] );
		$dom	= wecodeart( 'dom' )::create( $content );
		$div 	= wecodeart( 'dom' )::get_element( strtolower( $tagName ), $dom );

		// Toggle
		$toggler 	= wecodeart_template( 'general/toggler', $toggler, false );
		$toggler	= wecodeart( 'dom' )::create( $toggler );
		$toggler 	= wecodeart( 'dom' )::get_element( 'button', $toggler );
		$toggler->setAttribute( 'onload', 'this.parentNode.insertBefore(this,this.previousElementSibling);' );
		wecodeart( 'dom' )::add_classes( $toggler, [ $isHidden, 'collapsed' ] );
		$dom->appendChild( $dom->importNode( $toggler, true ) );

		$p = wecodeart( 'dom' )::processor( $dom->saveHTML() );

		// Container
		$p->next_tag( [ 'tag_name' => $tagName ] );
		$p->set_attribute( 'aria-labelledby', $itemID . '-toggle' );
		$p->set_attribute( 'id', $itemID . '-content' );
		$p->set_attribute( 'role', 'region' );
		$p->add_class( 'collapse' );

		if( $isDisplay ) {
			$p->add_class( $isDisplay );
			
			\wecodeart( 'styles' )->Utilities->load( [ $isDisplay ] );
		}
			
		// Requirements
		\wp_enqueue_script_module( '@wecodeart/collapse' );
		\wp_interactivity_config( 'wecodeart/collapse', [
			'ariaLabel'	=>	[
				'collapsed' => esc_html__( 'Open item', 'wecodeart' ),
				'expanded'	=> esc_html__( 'Close item', 'wecodeart' )
			],
			'classNames' => [
				'show' 			=> 'show',
				'collapse' 		=> 'collapse',
				'collapsing' 	=> 'collapsing'
			]
		] );

		\wecodeart( 'styles' )->Components->load( [ 'transition' ] );

		return $p->get_updated_html();
	}

	/**
	 * Generate offcanvas markup.
	 *
	 * @param 	array 	$attributes 	The block attrs.
	 * @param 	string 	$content 		The block markup.
	 *
	 * @return 	string 	The block updated markup.
	 */
	public static function create_offcanvas( array $attributes, string $content ): string {
		$tagName 	= get_prop( $attributes, [ 'tagName' ], 'div' );
		$className  = get_prop( $attributes, [ 'className' ], '' );
		$blockName  = get_prop( $attributes, [ 'metadata', 'name' ] ) ?: __( 'Group', 'wecodeart' );
		$itemID 	= wp_unique_id( 'wp-group-' );
		$isHidden 	= '';
		$isDisplay 	= '';

		// Offcanvas classes
		$classes	= explode( ' ', get_prop( $attributes, [ 'className' ], '' ) );
		$expanded   = array_extract( $classes, ':wp-offcanvas--expand' );
		$offcanvas  = array_extract( $classes, 'wp-offcanvas--' );
		$offcanvas  = $offcanvas ?: [ 'wp-offcanvas--start' ];

		$breakpoint = '';
		if( count( $expanded ) ) {
			$breakpoint = explode( ':', current( $expanded ) )[0];
			$breakpoint = 'd-' . $breakpoint . '-none';

			\wecodeart( 'styles' )->Utilities->load( [ $breakpoint ] );
		}

		$dom	= wecodeart( 'dom' )::create( $content );
		$div 	= wecodeart( 'dom' )::get_element( strtolower( $tagName ), $dom );

		// Create the toggler button
		$toggler = wecodeart_template( 'general/toggler', [
			'id'    => $itemID,
			'label' => sprintf( esc_html__( 'Open %s', 'wecodeart' ), $blockName ),
			'type'  => 'offcanvas',
			'class'	=> array_filter( [ 
				'wp-element-button',
				'has-' . get_prop( $attributes, [ 'backgroundColor' ], 'accent' ). '-background-color',
				'has-' . get_prop( $attributes, [ 'textColor' ], 'dark' ). '-color',
				$breakpoint
			] ),
		], false);
		$toggler = wecodeart( 'dom' )::create( $toggler );
		$toggler = wecodeart( 'dom' )::get_element( 'button', $toggler );

		// Insert the toggler button as the first child of the group
		$div->insertBefore( $dom->importNode( $toggler, true ), $div->firstChild );

		// Create the offcanvas container
		$container = wecodeart_template( 'general/offcanvas', [
			'id'      	=> $itemID,
			'title'   	=> get_prop( $attributes, [ 'metadata', 'name' ] ),
			'class'		=> array_merge( $offcanvas, $expanded ),
			'content' 	=> ''
		], false );
		$container = wecodeart( 'dom' )::create( $container );
		$offcanvas = wecodeart( 'dom' )::get_element( 'div', $container );
		
		$wrap = wecodeart( 'dom' )::get_elements_by_class( $container, 'wp-offcanvas__body', 'div', 0 );
		wecodeart( 'dom' )::add_classes( $wrap, [ 'is-layout-' . get_prop( $attributes, [ 'layout', 'type' ], 'flow' ) ] );

		$count 	= $div->childNodes->count();
		for ( $i = 0; $i < $count; $i++ ) {
			if( $i === 0 ) { // Skip toggler
				continue;
			}

			$item = $div->childNodes->item( $i );

			if ( ! $item || ! method_exists( $item, 'setAttribute' ) ) {
				continue;
			}

			$wrap->appendChild( $container->importNode( $item, true ) );
			$div->removeChild( $item );
		}

		// Append newly created offcanvas
		$div->appendChild( $dom->importNode( $offcanvas, true ) );

		return $dom->saveHTML();
	}

	/**
	 * Marquee styles
	 *
	 * @return 	string 	The block styles.
	 */
	public static function marquee_styles() {
		$mobile 	= wecodeart_json( [ 'settings', 'custom', 'mobileBreakpoint' ], 'lg' );
		$breakpoint = wecodeart_json( [ 'settings', 'custom', 'breakpoints', $mobile ], '992px' );

		$inline = "
			.wp-block-group--marquee {
				--marquee-animation: __marquee;
				--marquee-speed: 50s;
				--marquee-speed-mobile: calc(var(--marquee-speed) / 2);
				--marquee-state: running;
				--marquee-direction: forwards;
				--marquee-gap: var(--wp--style--block-gap);
				--marquee-transform: translate3d(calc(-100% - var(--marquee-gap)),0,0);
				--marquee-repeat: infinite;
				justify-content: initial /* required */;
				flex-wrap: nowrap /* required */;
				max-width: 100vw;
				overflow-x: hidden;
				overflow-x: clip;
			}
			.wp-block-group--marquee.is-vertical { 
				--marquee-transform: translate3d(0,calc(-100% - var(--marquee-gap)),0);
			}
			.wp-block-group--marquee:is(.scroll.scroll) {
				--marquee-state: initial;
				--marquee-repeat: initial;
				--marquee-timeline: scroll(root);
			}
			.wp-block-group--marquee > .wp-block-group__marquee {
				position: relative;
				display: flex;
				flex-direction: inherit;
				flex-shrink: 0;
				gap: inherit;
				min-height: 1em;
				transform: translate3d(0,0,0);
				animation-timeline: var(--marquee-timeline, initial);
				animation-name: var(--marquee-animation);
				animation-timing-function: linear;
				animation-duration: var(--marquee-speed-mobile);
				animation-direction: var(--marquee-direction);
				animation-play-state: var(--marquee-state);
				animation-iteration-count: var(--marquee-repeat);
			}
			.wp-block-group--marquee:is(:hover,:focus,:focus-within) {
				--marquee-state: paused;
			}
			@media (min-width: {$breakpoint}) {
				.wp-block-group--marquee > .wp-block-group__marquee {
					animation-duration: var(--marquee-speed);
				}
			}
			@media (prefers-reduced-motion) {
				.wp-block-group--marquee {
					--marquee-state: paused;
				}
			}
			@keyframes __marquee {
				to {
					transform: var(--marquee-transform);
				}
			}
		";

		$inline = wecodeart( 'styles' )::compress( $inline );

		return $inline;
	}

	/**
	 * Block styles
	 *
	 * @return 	string 	The block styles.
	 */
	public function styles() {
		return <<<CSS
			.wp-block-group {
				box-sizing: border-box;
			}
		CSS;
	}
}
