<?php
/**
 * Frontend: replace the_content when builder is enabled.
 * Loads full theme styles and suppresses duplicate FSE page chrome
 * so the published page matches the builder canvas + theme header/footer.
 *
 * @package AuraBuilder
 */

declare(strict_types=1);

namespace AuraBuilder\Frontend;


use AuraBuilder\Support\Assets;
use AuraBuilder\Support\Json;
use AuraBuilder\Document\Document;
use AuraBuilder\Renderer\DocumentRenderer;
use AuraBuilder\Storage\DocumentRepository;
use AuraBuilder\Theme\ThemeStyles;

final class ContentFilter {

	private ?Document $active_doc = null;

	/** Cached doc CSS for enqueue. */
	private string $pending_css = '';

	/** @var list<array<string,mixed>> */
	private array $pending_popups = array();

	/**
	 * Re-entry guard: DynamicWidget post_content calls apply_filters('the_content'),
	 * which must not re-apply the Single template (infinite loop / OOM).
	 */
	private static int $content_depth = 0;

	public function hooks(): void {
		// After wpautop (10) so builder HTML is not re-wrapped in <p>
		// (was wrapping “Leia mais” and breaking loop card layout).
		add_filter( 'the_content', array( $this, 'content' ), 12 );
		add_action( 'wp_enqueue_scripts', array( $this, 'assets' ), 20 );
		add_filter( 'body_class', array( $this, 'body_class' ) );
		add_filter( 'render_block', array( $this, 'filter_blocks' ), 10, 2 );
	}

	public function assets(): void {
		$builder_id   = $this->active_post_id();
		$need_widgets = (bool) $builder_id || $this->has_global_popups();
		// Always load base front.css first when builder content is present.
		// Missing this file was leaving home without grid/card/nav layout rules.
		if ( $builder_id || $need_widgets || $this->theme_chrome_active() ) {
			$this->enqueue_front_css();
		}
		if ( $builder_id ) {
			( new ThemeStyles() )->enqueue_front();
			$this->prepare_doc_css( $builder_id );
		}
		if ( $this->pending_css !== '' ) {
			// Ensure handle exists as dependency target.
			$this->enqueue_front_css();
			Assets::inline_style( 'aptb-doc', $this->pending_css, array( 'aura-builder-front' ) );
		}
		if ( ! $need_widgets ) {
			return;
		}
		$front_js = AURA_BUILDER_PATH . 'frontend/js/front-widgets.js';
		if ( is_file( $front_js ) ) {
			wp_enqueue_script(
				'aura-builder-front-widgets',
				AURA_BUILDER_URL . 'frontend/js/front-widgets.js',
				array(),
				(string) filemtime( $front_js ),
				true
			);
		}
		$this->prepare_popups();
		if ( $this->pending_popups ) {
			$json = wp_json_encode( array_values( $this->pending_popups ) );
			if ( is_string( $json ) ) {
				// Plugin-generated only: JSON of sanitized popup config.
				Assets::inline_script(
					'aura-builder-front-widgets',
					'window.AuraBuilderPopups=' . $json . ';',
					'before'
				);
			}
		}
	}

	private function prepare_doc_css( int $id ): void {
		$doc = $this->get_active_doc( $id );
		// Guard: render may expand dynamic post_content via the_content.
		++self::$content_depth;
		try {
			$out = ( new DocumentRenderer() )->render(
				$doc,
				array(
					'editor' => false,
					'postId' => $id,
				)
			);
		} finally {
			--self::$content_depth;
		}
		// Critical layout rules if front.css fails to print (cache/CDN/dep issues).
		$css  = $this->critical_layout_css();
		$css .= (string) ( $out['css'] ?? '' );
		// Enqueue Google Fonts as real stylesheet (not @import in inline CSS).
		$this->enqueue_doc_google_fonts( $doc );

		$h_src = (string) ( $doc->settings['header_source'] ?? 'global' );
		$f_src = (string) ( $doc->settings['footer_source'] ?? 'global' );
		if ( ! $doc->show_header() || $h_src === 'none' ) {
			$css .= 'body.ab-hide-header .wp-site-blocks > header:not(.ab-theme-builder-header),'
				. 'body.ab-hide-header header.wp-block-template-part,'
				. 'body.ab-hide-header .site-header:not(.ab-theme-builder-header),'
				. 'body.ab-hide-header #masthead:not(.ab-theme-builder-header){display:none!important;}';
		}
		if ( ! $doc->show_footer() || $f_src === 'none' ) {
			$css .= 'body.ab-hide-footer .wp-site-blocks > footer:not(.ab-theme-builder-footer),'
				. 'body.ab-hide-footer footer.wp-block-template-part,'
				. 'body.ab-hide-footer .site-footer:not(.ab-theme-builder-footer),'
				. 'body.ab-hide-footer #colophon:not(.ab-theme-builder-footer){display:none!important;}';
		}
		$css .= 'body.ab-builder-page .ab-hide-template-title{display:none!important;}';
		$this->pending_css = $css;
	}

	private function prepare_popups(): void {
		$option = get_option( 'aptb_site_popups', null );
		if ( ! is_array( $option ) ) {
			$option = get_option( 'aura_builder_site_popups', array() );
		}
		if ( ! is_array( $option ) || ! $option ) {
			$this->pending_popups = array();
			return;
		}
		$is_home = is_front_page() || is_home();
		$payload = array();
		foreach ( $option as $row ) {
			if ( ! is_array( $row ) ) {
				continue;
			}
			$scope = sanitize_key( (string) ( $row['scope'] ?? '' ) );
			if ( $scope === 'home' && ! $is_home ) {
				continue;
			}
			if ( $scope !== 'home' && $scope !== 'all' ) {
				continue;
			}
			$src_post = absint( $row['post_id'] ?? 0 );
			if ( $src_post > 0 && is_singular() && (int) get_queried_object_id() === $src_post ) {
				continue;
			}
			// Sanitize payload scalars for JS.
			$clean = array();
			foreach ( $row as $k => $v ) {
				$key = sanitize_key( (string) $k );
				if ( is_scalar( $v ) || $v === null ) {
					$clean[ $key ] = is_string( $v ) ? sanitize_text_field( $v ) : $v;
				}
			}
			$payload[] = $clean;
		}
		$this->pending_popups = $payload;
	}

	/**
	 * @param list<string> $classes
	 * @return list<string>
	 */
	public function body_class( array $classes ): array {
		$id = $this->active_post_id();
		if ( ! $id ) {
			return $classes;
		}
		$doc         = $this->get_active_doc( $id );
		$classes[]   = 'ab-builder-page';
		$classes[]   = 'ab-theme-surface';
		if ( ! $doc->show_header() ) {
			$classes[] = 'ab-hide-header';
		}
		if ( ! $doc->show_footer() ) {
			$classes[] = 'ab-hide-footer';
		}
		return $classes;
	}

	/**
	 * @param string               $content Block HTML.
	 * @param array<string, mixed> $block   Parsed block.
	 */
	public function filter_blocks( string $content, array $block ): string {
		$id = $this->active_post_id();
		if ( ! $id ) {
			return $content;
		}

		$name = (string) ( $block['blockName'] ?? '' );
		$doc  = $this->get_active_doc( $id );

		// Theme header / footer toggles — never strip already-injected Theme Builder chrome.
		if ( $name === 'core/template-part' ) {
			$slug = (string) ( $block['attrs']['slug'] ?? '' );
			if ( $slug === 'header' ) {
				if ( str_contains( $content, 'ab-theme-builder-header' ) ) {
					return $content;
				}
				if ( ! $doc->show_header() ) {
					return '';
				}
			}
			if ( $slug === 'footer' ) {
				if ( str_contains( $content, 'ab-theme-builder-footer' ) ) {
					return $content;
				}
				if ( ! $doc->show_footer() ) {
					return '';
				}
			}
			return $content;
		}

		// Page template extras — builder already is the full main content.
		// Remove duplicate title / featured image / leading separator from FSE page template.
		if ( in_array( $name, array( 'core/post-title', 'core/post-featured-image' ), true ) ) {
			return '';
		}
		// Avoid double comment form (theme + Single widget).
		if ( in_array(
			$name,
			array( 'core/comments', 'core/post-comments', 'core/post-comments-form', 'core/comments-query-loop' ),
			true
		) ) {
			return '';
		}

		// Hide wide separators that sit above post content in default page template.
		if ( $name === 'core/separator' && $this->is_main_query_content_area() ) {
			// Only strip when it is a direct template chrome separator (heuristic: wide style).
			$class = (string) ( $block['attrs']['className'] ?? '' );
			$align = (string) ( $block['attrs']['align'] ?? '' );
			if ( $align === 'wide' || str_contains( $class, 'is-style-wide' ) ) {
				return '';
			}
		}

		// Spacer often placed under title in page template.
		if ( $name === 'core/spacer' && $this->is_main_query_content_area() ) {
			// Keep spacers inside our builder HTML (not blocks). Template spacers before content:
			// only remove small 32px spacers common in TT2 page template.
			$h = $block['attrs']['height'] ?? null;
			if ( $h === 32 || $h === '32' || $h === '32px' ) {
				return '';
			}
		}

		return $content;
	}

	public function content( string $content ): string {
		// Nested the_content (e.g. dynamic post_content widget) → never re-wrap.
		if ( self::$content_depth > 0 || ! empty( $GLOBALS['ab_skip_content_filter'] ) ) {
			return $content;
		}
		if ( ! is_singular() || ! in_the_loop() || ! is_main_query() ) {
			return $content;
		}
		$id = get_the_ID();
		if ( ! $id ) {
			return $content;
		}
		$post_id = (int) $id;

		// Own Builder layout always wins.
		if ( $this->has_own_builder_layout( $post_id ) ) {
			return $this->render_builder_content( $this->get_active_doc( $post_id ), $post_id );
		}

		// Theme Builder · Single: posts AND pages without their own layout
		// (school sites are mostly pages — Single was previously post-only).
		$single_doc = $this->resolve_single_template_doc( $post_id );
		if ( $single_doc ) {
			return $this->render_builder_content( $single_doc, $post_id );
		}

		return $content;
	}

	/**
	 * Render with re-entry guard so nested the_content does not loop.
	 *
	 * @param Document $doc Document tree.
	 */
	private function render_builder_content( Document $doc, int $post_id ): string {
		++self::$content_depth;
		try {
			return $this->wrap_builder_output( $doc, $post_id );
		} finally {
			--self::$content_depth;
		}
	}

	/**
	 * @param \AuraBuilder\Document\Document $doc
	 */
	private function wrap_builder_output( $doc, int $post_id ): string {
		$out = ( new DocumentRenderer() )->render(
			$doc,
			array(
				'editor' => false,
				'postId' => $post_id,
			)
		);

		// Full-width wrap: root/section control max-width (not theme constrained ~650px).
		// Keep entry-content for typography tokens; avoid is-layout-constrained which
		// squeezes the builder and creates side gaps that the editor does not have.
		$carousel = $this->site_bg_carousel_html( $doc->settings );
		$html     = (string) ( $out['html'] ?? '' );
		// Extract <style>/<script> BEFORE kses: wp_kses strips tags but leaves
		// their text, which made CSS/JS dump as visible page content.
		$extracted = Assets::extract_style_and_script_blocks( $html );
		$html      = $extracted['html'];
		if ( $extracted['css'] !== '' ) {
			Assets::inline_style(
				'aptb-content-extra',
				$extracted['css'],
				array( 'aura-builder-front' )
			);
		}
		// Re-attach trusted scripts (e.g. WP comment_form) after kses.
		$html     = Assets::kses_builder( $html, false ) . $extracted['scripts'];
		$carousel = Assets::kses_builder( $carousel );
		return '<div class="ab-content-wrap entry-content wp-block-post-content ab-theme-surface alignfull">'
			. $carousel
			. $html
			. '</div>';
	}

	/**
	 * @param array<string, mixed> $settings Document settings.
	 */
	private function site_bg_carousel_html( array $settings ): string {
		$mode = sanitize_key( (string) ( $settings['site_bg_mode'] ?? 'color' ) );
		if ( $mode !== 'carousel' ) {
			return '';
		}
		$raw = $settings['site_bg_images'] ?? '[]';
		if ( is_array( $raw ) ) {
			$raw = Json::encode( $raw, '[]' );
		}
		$decoded = json_decode( (string) $raw, true );
		if ( ! is_array( $decoded ) || ! $decoded ) {
			return '';
		}
		$urls = array();
		foreach ( $decoded as $item ) {
			if ( is_string( $item ) && $item !== '' ) {
				$urls[] = esc_url( $item );
			} elseif ( is_array( $item ) && ! empty( $item['url'] ) ) {
				$urls[] = esc_url( (string) $item['url'] );
			}
		}
		$urls = array_values( array_filter( $urls ) );
		if ( ! $urls ) {
			return '';
		}
		$size  = trim( (string) ( $settings['site_bg_size'] ?? 'cover' ) );
		if ( ! in_array( $size, array( 'cover', 'contain', 'auto', '100% 100%' ), true )
			&& ! preg_match( '/^[\d.]+%\s*([\d.]+%|auto)?$/', $size ) ) {
			$size = 'cover';
		}
		$pos   = sanitize_text_field( (string) ( $settings['site_bg_position'] ?? 'center center' ) );
		$speed = max( 1500, min( 30000, (int) ( $settings['site_bg_carousel_speed'] ?? 5000 ) ) );
		$fx    = sanitize_key( (string) ( $settings['site_bg_carousel_fx'] ?? 'fade' ) );
		if ( ! in_array( $fx, array( 'fade', 'slide', 'zoom' ), true ) ) {
			$fx = 'fade';
		}
		$html  = '<div class="ab-site-bg-carousel" data-ab-site-bg-carousel="1" data-speed="'
			. esc_attr( (string) $speed ) . '" data-fx="' . esc_attr( $fx ) . '" aria-hidden="true">';
		foreach ( $urls as $i => $u ) {
			$html .= '<div class="ab-site-bg-carousel__slide' . ( $i === 0 ? ' is-active' : '' )
				. '" style="background-image:url(\'' . esc_url( $u ) . '\');background-size:'
				. esc_attr( $size ) . ';background-position:' . esc_attr( $pos )
				. ';background-repeat:no-repeat"></div>';
		}
		$html .= '</div>';
		return $html;
	}

	/** Enqueue frontend/css/front.css once (layout critical). */
	private function enqueue_front_css(): void {
		Assets::force_https_assets();
		$front_css = AURA_BUILDER_PATH . 'frontend/css/front.css';
		if ( ! is_file( $front_css ) ) {
			return;
		}
		$ver = (string) filemtime( $front_css );
		// Always re-register with https-safe file URL.
		wp_register_style(
			'aura-builder-front',
			Assets::url( 'frontend/css/front.css' ),
			array(),
			$ver
		);
		wp_enqueue_style( 'aura-builder-front' );
	}

	/**
	 * Minimal grid/flex rules so layout survives if front.css is stripped by optimizers.
	 */
	private function critical_layout_css(): string {
		return '.ab-document{width:100%;box-sizing:border-box;display:flow-root}'
			. '.ab-node{position:relative;box-sizing:border-box;float:none!important;clear:both;max-width:100%;min-width:0}'
			. '.ab-type-section.ab-section--grid,.ab-type-section[style*="grid-template-columns"]{'
			. 'display:grid!important;grid-template-columns:repeat(12,minmax(0,1fr))!important;'
			. 'align-items:stretch;width:100%;box-sizing:border-box}'
			. '.ab-type-section.ab-section--grid>.ab-type-container{clear:none!important;float:none!important;min-width:0;max-width:100%}'
			. '.ab-type-section.ab-section--row,.ab-type-section[style*="flex-direction:row"]{'
			. 'display:flex!important;flex-direction:row!important;flex-wrap:wrap;align-items:center;width:100%}'
			. '.ab-type-root{display:flex;flex-direction:column;width:100%}'
			. '.ab-image-box,.ab-icon-box{display:flex;flex-direction:column;width:100%;box-sizing:border-box}'
			. '.ab-nav-menu__list{display:flex;flex-wrap:wrap;align-items:center;list-style:none;margin:0;padding:0;gap:8px}'
			. '@media(min-width:768px){.ab-nav-menu--horizontal .ab-nav-menu__list{flex-direction:row!important}'
			. '.ab-nav-menu--horizontal .ab-nav-menu__item{width:auto!important;flex:0 0 auto!important}}'
			. '@media(max-width:767px){.ab-document--front .ab-type-section{grid-template-columns:1fr!important}'
			. '.ab-document--front .ab-type-section>.ab-type-container{grid-column:1/-1!important;width:100%!important}}';
	}

	/** @param Document $doc Document */
	private function enqueue_doc_google_fonts( Document $doc ): void {
		$engine = new \AuraBuilder\Renderer\StyleEngine();
		$url    = \AuraBuilder\Renderer\StyleEngine::google_fonts_link( $engine->google_font_families( $doc ) );
		if ( $url === '' || ! str_starts_with( $url, 'https://fonts.googleapis.com/' ) ) {
			return;
		}
		wp_enqueue_style( 'aura-builder-google-fonts', $url, array(), null );
	}

	/** Header or footer Theme Builder is active on this request. */
	private function theme_chrome_active(): bool {
		return \AuraBuilder\Theme\ThemeBuilder::should_replace_on_front( 'header' )
			|| \AuraBuilder\Theme\ThemeBuilder::should_replace_on_front( 'footer' );
	}

	private function has_global_popups(): bool {
		$global = get_option( 'aptb_site_popups', null );
		if ( ! is_array( $global ) ) {
			$global = get_option( 'aura_builder_site_popups', array() );
		}
		return is_array( $global ) && count( $global ) > 0;
	}

	private function is_main_query_content_area(): bool {
		return is_singular() && in_the_loop() && is_main_query();
	}

	private function active_post_id(): int {
		if ( ! is_singular() ) {
			return 0;
		}
		$id = (int) get_queried_object_id();
		if ( $id <= 0 ) {
			return 0;
		}
		if ( $this->has_own_builder_layout( $id ) ) {
			return $id;
		}
		// CSS / body_class also when Single template covers this singular.
		return $this->resolve_single_template_doc( $id ) ? $id : 0;
	}

	private function has_own_builder_layout( int $post_id ): bool {
		return $post_id > 0 && ( new DocumentRepository() )->is_enabled( $post_id );
	}

	/**
	 * Theme Builder Single document when it should replace theme content.
	 */
	private function resolve_single_template_doc( int $post_id ): ?Document {
		if ( $post_id <= 0 || ! is_singular() ) {
			return null;
		}
		// Only standard singular types (not attachments etc.).
		if ( ! is_singular( array( 'post', 'page' ) ) ) {
			return null;
		}
		$single = \AuraBuilder\Theme\ThemeBuilder::get_part( 'single' );
		if ( empty( $single['enabled'] ) ) {
			return null;
		}
		if ( ! \AuraBuilder\Theme\ThemeBuilder::should_display( $single, $post_id ) ) {
			return null;
		}
		$doc_raw = is_array( $single['document'] ?? null ) ? $single['document'] : array();
		if ( empty( $doc_raw['nodes'] ) ) {
			return null;
		}
		$doc                     = \AuraBuilder\Document\DocumentSanitizer::sanitize( $doc_raw );
		$doc->metadata['postId'] = $post_id;
		$doc->metadata['kind']   = 'theme_single';
		// Chrome: part-level override (global = Theme Builder header/footer default).
		// Never inherit the old starter "showHeader: false" that hid site chrome.
		$h_src = sanitize_key( (string) ( $single['header_source'] ?? 'global' ) );
		$f_src = sanitize_key( (string) ( $single['footer_source'] ?? 'global' ) );
		if ( ! in_array( $h_src, array( 'global', 'builder', 'theme', 'none' ), true ) ) {
			$h_src = 'global';
		}
		if ( ! in_array( $f_src, array( 'global', 'builder', 'theme', 'none' ), true ) ) {
			$f_src = 'global';
		}
		$doc->settings['header_source'] = $h_src;
		$doc->settings['footer_source'] = $f_src;
		$doc->settings['showHeader']    = $h_src !== 'none';
		$doc->settings['showFooter']    = $f_src !== 'none';
		return $doc;
	}

	private function get_active_doc( int $id ): Document {
		// Cache key: prefer loaded doc for this post context.
		if ( $this->active_doc && (int) ( $this->active_doc->metadata['postId'] ?? 0 ) === $id ) {
			return $this->active_doc;
		}
		if ( $this->has_own_builder_layout( $id ) ) {
			$doc                     = ( new DocumentRepository() )->get( $id );
			$doc->metadata['postId'] = $id;
			$this->active_doc        = $doc;
			return $doc;
		}
		$single = $this->resolve_single_template_doc( $id );
		if ( $single ) {
			$this->active_doc = $single;
			return $single;
		}
		// Fallback empty own doc (should rarely be hit).
		$doc                     = ( new DocumentRepository() )->get( $id );
		$doc->metadata['postId'] = $id;
		$this->active_doc        = $doc;
		return $doc;
	}
}
