<?php
/**
 * Footer column visibility by page context.
 *
 * @package Rsmspark_Content_Widgets
 */

defined( 'ABSPATH' ) || exit;

/**
 * Rsmspark_Content_Widgets_Footer_Visibility
 */
class Rsmspark_Content_Widgets_Footer_Visibility {

	/**
	 * Sidebar ids that already rendered a footer with explicit visibility rules.
	 *
	 * @var array<string, bool>
	 */
	private static $sidebar_explicit_match = array();

	/**
	 * Widget area currently being rendered (for block widgets in sidebars).
	 *
	 * @var string
	 */
	private static $current_sidebar_id = '';

	/**
	 * Register runtime hooks.
	 *
	 * @return void
	 */
	public static function init() {
		add_action( 'dynamic_sidebar_before', array( __CLASS__, 'on_sidebar_before' ), 1 );
		add_action( 'dynamic_sidebar_after', array( __CLASS__, 'on_sidebar_after' ), 99 );
	}

	/**
	 * Widget area id while dynamic_sidebar() is rendering (empty otherwise).
	 *
	 * @return string
	 */
	public static function get_current_sidebar_id() {
		return (string) self::$current_sidebar_id;
	}

	/**
	 * @param int|string $index Sidebar index.
	 * @return void
	 */
	public static function on_sidebar_before( $index ) {
		self::$current_sidebar_id = (string) $index;
		self::$sidebar_explicit_match[ self::$current_sidebar_id ] = false;
	}

	/**
	 * @return void
	 */
	public static function on_sidebar_after() {
		self::$current_sidebar_id = '';
	}

	/**
	 * Reset per-sidebar explicit footer tracking.
	 *
	 * @param int|string $index Sidebar index.
	 * @return void
	 */
	public static function reset_sidebar_tracking( $index ) {
		self::$sidebar_explicit_match[ (string) $index ] = false;
	}

	/**
	 * @param array<string, mixed> $instance Sanitized or raw footer instance.
	 * @return bool
	 */
	public static function instance_has_visibility_rules( array $instance ) {
		return ! empty( self::read_page_type_flags( $instance, 'show_on' ) )
			|| ! empty( self::read_page_type_flags( $instance, 'hide_on' ) )
			|| ! empty( self::sanitize_post_types( $instance['show_on_post_types'] ?? array() ) )
			|| ! empty( self::sanitize_post_types( $instance['hide_on_post_types'] ?? array() ) );
	}

	/**
	 * @return array<string, string> slug => label
	 */
	public static function page_type_options() {
		$options = array(
			'front_page' => __( 'Front page', 'rsm-content-widgets' ),
			'blog'         => __( 'Blog posts index', 'rsm-content-widgets' ),
			'singular'     => __( 'Single posts & pages', 'rsm-content-widgets' ),
			'page'         => __( 'Pages', 'rsm-content-widgets' ),
			'post'         => __( 'Blog posts', 'rsm-content-widgets' ),
			'archive'      => __( 'Archives', 'rsm-content-widgets' ),
			'search'       => __( 'Search results', 'rsm-content-widgets' ),
			'404'          => __( '404 pages', 'rsm-content-widgets' ),
		);

		if ( class_exists( 'WooCommerce', false ) ) {
			$options['shop']     = __( 'Shop (WooCommerce)', 'rsm-content-widgets' );
			$options['product']  = __( 'Product pages', 'rsm-content-widgets' );
			$options['cart']     = __( 'Cart', 'rsm-content-widgets' );
			$options['checkout'] = __( 'Checkout', 'rsm-content-widgets' );
			$options['account']  = __( 'My account', 'rsm-content-widgets' );
		}

		$options['logged_in']  = __( 'Logged-in visitors', 'rsm-content-widgets' );
		$options['logged_out'] = __( 'Logged-out visitors', 'rsm-content-widgets' );

		return (array) apply_filters( 'rsmspark_content_widgets_footer_page_type_options', $options );
	}

	/**
	 * @return array<string, string> post_type => label
	 */
	public static function post_type_options() {
		$options = array();
		$types   = get_post_types( array( 'public' => true ), 'objects' );

		foreach ( $types as $type ) {
			if ( in_array( $type->name, array( 'attachment', 'nav_menu_item' ), true ) ) {
				continue;
			}
			$options[ $type->name ] = $type->labels->singular_name ?: $type->label;
		}

		asort( $options );

		return (array) apply_filters( 'rsmspark_content_widgets_footer_post_type_options', $options );
	}

	/**
	 * @param mixed $types Raw post type list.
	 * @return array<int, string>
	 */
	public static function sanitize_post_types( $types ) {
		if ( ! is_array( $types ) ) {
			if ( is_string( $types ) && '' !== trim( $types ) ) {
				$types = array_map( 'trim', explode( ',', $types ) );
			} else {
				return array();
			}
		}

		$allowed = array_keys( self::post_type_options() );
		$out     = array();

		foreach ( $types as $type ) {
			$type = sanitize_key( (string) $type );
			if ( '' === $type || ! in_array( $type, $allowed, true ) ) {
				continue;
			}
			$out[] = $type;
		}

		return array_values( array_unique( $out ) );
	}

	/**
	 * @param mixed $types Raw type list from forms or JSON.
	 * @return array<int, string>
	 */
	public static function sanitize_types( $types ) {
		$allowed = array_keys( self::page_type_options() );
		$labels  = array();
		foreach ( self::page_type_options() as $slug => $label ) {
			$labels[ strtolower( $label ) ] = $slug;
		}
		$out     = array();

		$candidates = self::expand_type_candidates( $types );
		foreach ( $candidates as $candidate ) {
			$slug = self::normalize_type_slug( (string) $candidate, $allowed, $labels );
			if ( '' !== $slug ) {
				$out[] = $slug;
			}
		}

		return array_values( array_unique( $out ) );
	}

	/**
	 * Flatten raw visibility values from arrays, SiteOrigin multi-select, or strings.
	 *
	 * @param mixed $types Raw value.
	 * @return array<int, string>
	 */
	public static function expand_type_candidates( $types ) {
		if ( is_string( $types ) ) {
			$types = trim( $types );
			if ( '' === $types ) {
				return array();
			}

			if ( str_starts_with( $types, '[' ) || str_starts_with( $types, '{' ) ) {
				$decoded = json_decode( $types, true );
				if ( is_array( $decoded ) ) {
					return self::expand_type_candidates( $decoded );
				}
			}

			return array_map( 'trim', explode( ',', $types ) );
		}

		if ( ! is_array( $types ) ) {
			return array();
		}

		$out = array();
		foreach ( $types as $key => $value ) {
			if ( is_array( $value ) ) {
				$out = array_merge( $out, self::expand_type_candidates( $value ) );
				continue;
			}

			if ( is_string( $key ) && ! is_numeric( $key ) ) {
				$out[] = (string) $key;
			}

			if ( is_scalar( $value ) ) {
				$out[] = (string) $value;
			}
		}

		return $out;
	}

	/**
	 * @param string               $candidate Raw slug or label.
	 * @param array<int, string>   $allowed   Allowed slugs.
	 * @param array<string, string> $labels   label => slug map.
	 * @return string
	 */
	public static function normalize_type_slug( $candidate, array $allowed, array $labels ) {
		$candidate = trim( $candidate );
		if ( '' === $candidate ) {
			return '';
		}

		$slug = sanitize_key( $candidate );
		if ( in_array( $slug, $allowed, true ) ) {
			return $slug;
		}

		// Friendly aliases.
		if ( in_array( $slug, array( 'home', 'homepage', 'home-page' ), true ) ) {
			return 'front_page';
		}

		$label_key = strtolower( trim( html_entity_decode( $candidate, ENT_QUOTES ) ) );
		if ( isset( $labels[ $label_key ] ) ) {
			return $labels[ $label_key ];
		}

		foreach ( self::page_type_options() as $option_slug => $label ) {
			if ( strtolower( $label ) === $label_key ) {
				return $option_slug;
			}
		}

		return '';
	}

	/**
	 * Checkbox field prefix helper for forms: show_on_front_page, hide_on_404, etc.
	 *
	 * @param string $field Field prefix (show_on|hide_on).
	 * @return string
	 */
	public static function checkbox_field_name( $field, $slug ) {
		return sanitize_key( (string) $field ) . '_' . sanitize_key( (string) $slug );
	}

	/**
	 * Flatten SiteOrigin section fields onto the instance root for visibility reads.
	 *
	 * @param array<string, mixed> $instance Raw instance.
	 * @return array<string, mixed>
	 */
	public static function flatten_instance( array $instance ) {
		$flat = $instance;

		foreach ( $instance as $key => $value ) {
			if ( ! is_array( $value ) ) {
				continue;
			}

			// Skip repeater rows (columns, social, etc.).
			if ( is_int( $key ) || ( is_string( $key ) && is_numeric( $key ) ) ) {
				continue;
			}

			foreach ( $value as $nested_key => $nested_value ) {
				if ( ! is_string( $nested_key ) || is_numeric( $nested_key ) ) {
					continue;
				}
				if ( ! isset( $flat[ $nested_key ] ) ) {
					$flat[ $nested_key ] = $nested_value;
				}
			}
		}

		return $flat;
	}

	/**
	 * Read checkbox-per-type fields from a widget/block instance.
	 *
	 * @param array<string, mixed> $instance Raw instance.
	 * @param string               $field    show_on|hide_on.
	 * @return array<int, string>
	 */
	public static function read_page_type_flags( array $instance, $field ) {
		$flat  = self::flatten_instance( $instance );
		$field = sanitize_key( (string) $field );

		if ( isset( $flat[ $field ] ) ) {
			$parsed = self::sanitize_types( $flat[ $field ] );
			if ( ! empty( $parsed ) ) {
				return $parsed;
			}
		}

		$out = array();
		foreach ( array_keys( self::page_type_options() ) as $slug ) {
			$key = self::checkbox_field_name( $field, $slug );
			if ( ! empty( $flat[ $key ] ) ) {
				$out[] = $slug;
			}
		}

		return self::sanitize_types( $out );
	}

	/**
	 * Build a stored show_on/hide_on array from submitted checkbox fields.
	 *
	 * @param array<string, mixed> $submitted Raw form submission.
	 * @param string               $field     show_on|hide_on.
	 * @return array<int, string>
	 */
	public static function collect_page_type_flags( array $submitted, $field ) {
		if ( isset( $submitted[ $field ] ) && is_array( $submitted[ $field ] ) ) {
			return self::sanitize_types( $submitted[ $field ] );
		}

		$flags = array();
		foreach ( array_keys( self::page_type_options() ) as $slug ) {
			$key = self::checkbox_field_name( $field, $slug );
			if ( ! empty( $submitted[ $key ] ) ) {
				$flags[ $key ] = 1;
			}
		}

		return self::read_page_type_flags( $flags, $field );
	}

	/**
	 * SiteOrigin checkbox fields for page-type visibility.
	 *
	 * @param string               $field    show_on|hide_on.
	 * @param array<string, mixed> $instance Widget instance.
	 * @return array<string, mixed>
	 */
	public static function page_type_checkbox_fields( $field, array $instance = array() ) {
		$selected = self::read_page_type_flags( $instance, $field );
		$fields   = array();

		foreach ( self::page_type_options() as $slug => $label ) {
			$fields[ self::checkbox_field_name( $field, $slug ) ] = array(
				'type'    => 'checkbox',
				'label'   => $label,
				'default' => in_array( $slug, $selected, true ),
			);
		}

		return $fields;
	}

	/**
	 * Active page-type slugs for the current request.
	 *
	 * @return array<int, string>
	 */
	public static function active_page_types() {
		$active = array();

		if ( is_front_page() ) {
			$active[] = 'front_page';
		}

		if ( is_home() && ! is_front_page() ) {
			$active[] = 'blog';
		}

		if ( is_singular() ) {
			$active[] = 'singular';
		}

		if ( is_page() ) {
			$active[] = 'page';
		}

		if ( is_single() && 'post' === get_post_type() ) {
			$active[] = 'post';
		}

		if ( is_archive() ) {
			$active[] = 'archive';
		}

		if ( is_search() ) {
			$active[] = 'search';
		}

		if ( is_404() ) {
			$active[] = '404';
		}

		if ( function_exists( 'is_shop' ) && is_shop() ) {
			$active[] = 'shop';
		}

		if ( function_exists( 'is_product' ) && is_product() ) {
			$active[] = 'product';
		}

		if ( function_exists( 'is_cart' ) && is_cart() ) {
			$active[] = 'cart';
		}

		if ( function_exists( 'is_checkout' ) && is_checkout() ) {
			$active[] = 'checkout';
		}

		if ( function_exists( 'is_account_page' ) && is_account_page() ) {
			$active[] = 'account';
		}

		if ( is_user_logged_in() ) {
			$active[] = 'logged_in';
		} else {
			$active[] = 'logged_out';
		}

		return (array) apply_filters( 'rsmspark_content_widgets_footer_active_page_types', array_values( array_unique( $active ) ) );
	}

	/**
	 * Whether visibility rules should be evaluated on this request.
	 *
	 * @return bool
	 */
	public static function should_apply_visibility_filter() {
		if ( (bool) apply_filters( 'rsmspark_content_widgets_footer_bypass_column_visibility', false ) ) {
			return false;
		}

		// Avoid wp_is_serving_rest_request() (WP 6.5+) while Requires at least is 6.0.
		$is_rest = defined( 'REST_REQUEST' ) && REST_REQUEST;
		if ( $is_rest ) {
			$request_uri = isset( $_SERVER['REQUEST_URI'] )
				? sanitize_text_field( wp_unslash( (string) $_SERVER['REQUEST_URI'] ) )
				: '';
			if ( str_contains( $request_uri, '/wp/v2/block-renderer/' ) ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * @deprecated 2.31.0 Use should_apply_visibility_filter().
	 * @return bool
	 */
	public static function should_apply_column_filter() {
		return self::should_apply_visibility_filter();
	}

	/**
	 * @param array<int, string> $show_on Page-type slugs.
	 * @param array<int, string> $hide_on Page-type slugs.
	 * @param array<int, string> $show_on_post_types Post type slugs.
	 * @param array<int, string> $hide_on_post_types Post type slugs.
	 * @return bool
	 */
	public static function matches_visibility_rules( array $show_on, array $hide_on, array $show_on_post_types, array $hide_on_post_types ) {
		if ( empty( $show_on ) && empty( $hide_on ) && empty( $show_on_post_types ) && empty( $hide_on_post_types ) ) {
			return true;
		}

		$active    = self::active_page_types();
		$post_type = is_singular() ? (string) get_post_type() : '';

		if ( ! empty( $hide_on ) && array_intersect( $hide_on, $active ) ) {
			return false;
		}

		if ( ! empty( $hide_on_post_types ) && '' !== $post_type && in_array( $post_type, $hide_on_post_types, true ) ) {
			return false;
		}

		if ( ! empty( $show_on ) && ! array_intersect( $show_on, $active ) ) {
			return false;
		}

		if ( ! empty( $show_on_post_types ) ) {
			if ( '' === $post_type || ! in_array( $post_type, $show_on_post_types, true ) ) {
				return false;
			}
		}

		return true;
	}

	/**
	 * @param array<string, mixed> $instance Sanitized footer instance.
	 * @param string               $sidebar_id Optional widget area id/index.
	 * @return bool
	 */
	public static function instance_is_visible( array $instance, $sidebar_id = '' ) {
		if ( ! self::should_apply_visibility_filter() ) {
			return true;
		}

		$show_on            = self::read_page_type_flags( $instance, 'show_on' );
		$hide_on            = self::read_page_type_flags( $instance, 'hide_on' );
		$show_on_post_types = self::sanitize_post_types( $instance['show_on_post_types'] ?? array() );
		$hide_on_post_types = self::sanitize_post_types( $instance['hide_on_post_types'] ?? array() );

		$visible = self::matches_visibility_rules(
			$show_on,
			$hide_on,
			$show_on_post_types,
			$hide_on_post_types
		);

		$has_rules   = ! empty( $show_on ) || ! empty( $hide_on ) || ! empty( $show_on_post_types ) || ! empty( $hide_on_post_types );
		$sidebar_key = sanitize_key( (string) $sidebar_id );
		if ( '' === $sidebar_key ) {
			$sidebar_key = sanitize_key( self::$current_sidebar_id );
		}

		if ( $visible && $has_rules && '' !== $sidebar_key ) {
			self::$sidebar_explicit_match[ $sidebar_key ] = true;
		}

		if ( $visible && ! $has_rules && '' !== $sidebar_key && ! empty( self::$sidebar_explicit_match[ $sidebar_key ] ) ) {
			$visible = false;
		}

		return (bool) apply_filters( 'rsmspark_content_widgets_footer_instance_is_visible', $visible, $instance, $sidebar_id );
	}

	/**
	 * @param array<string, mixed> $column Sanitized column row.
	 * @return bool
	 */
	public static function column_is_visible( array $column ) {
		return self::matches_visibility_rules(
			self::sanitize_types( $column['show_on'] ?? array() ),
			self::sanitize_types( $column['hide_on'] ?? array() ),
			array(),
			array()
		);
	}

	/**
	 * @param array<int, array<string, mixed>> $columns Sanitized columns.
	 * @return array<int, array<string, mixed>>
	 */
	public static function filter_columns( array $columns ) {
		if ( ! self::should_apply_column_filter() ) {
			return $columns;
		}

		$filtered = array();
		foreach ( $columns as $column ) {
			if ( ! is_array( $column ) ) {
				continue;
			}
			if ( self::column_is_visible( $column ) ) {
				$filtered[] = $column;
			}
		}

		return $filtered;
	}
}
