<?php

namespace Jet_Menu\Modules\Dynamic_Visibility;

// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
	die;
}

class Dynamic_Visibility {

	public function __construct() {

		require jet_menu()->plugin_path( 'includes/modules/dynamic-visibility/inc/context.php' );
		require jet_menu()->plugin_path( 'includes/modules/dynamic-visibility/inc/checker.php' );
		require jet_menu()->plugin_path( 'includes/modules/dynamic-visibility/inc/conditions/registry.php' );

		// Frontend only filter.
		if ( ! is_admin() ) {
			add_filter( 'wp_get_nav_menu_items', array( $this, 'apply_visibility_get_items' ), 20, 3 );
		}
	}

	/**
	 * Filter for wp_get_nav_menu_items.
	 *
	 * @param array $items
	 * @param object $menu
	 * @param array $args
	 *
	 * @return array
	 */
	public function apply_visibility_get_items( $items, $menu, $args ) {

		// Normalize args: for our context it can be array.
		$args_obj = is_object( $args ) ? $args : (object) $args;

		return $this->apply_visibility( $items, $args_obj );
	}

	/**
	 * Main filter: remove menu items based on DV rules.
	 *
	 * @param array $items
	 * @param object $args
	 *
	 * @return array
	 */
	public function apply_visibility( $items, $args ) {

		if ( empty( $items ) || ! is_array( $items ) ) {
			return $items;
		}

		$checker    = new Checker();
		$remove_ids = array();

		foreach ( $items as $item ) {

			$item_id = isset( $item->ID ) ? absint( $item->ID ) : 0;

			if ( ! $item_id ) {
				continue;
			}

			$settings = get_post_meta( $item_id, 'jet_menu_settings', true );
			$settings = is_array( $settings ) ? $settings : array();

			$dv = isset( $settings['dynamic_visibility'] ) ? $settings['dynamic_visibility'] : array();
			$dv = is_array( $dv ) ? $dv : array();

			$enabled = isset( $dv['enabled'] ) ? $dv['enabled'] : false;
			$enabled = filter_var( $enabled, FILTER_VALIDATE_BOOLEAN );
			$enabled = ( null === $enabled ) ? false : $enabled;

			if ( ! $enabled ) {
				continue;
			}

			$context = new Context( array(
				'item'      => $item,
				'menu_args' => $args,
			) );

			$is_match = $checker->check( $dv, $context );

			$type = isset( $dv['type'] ) ? $dv['type'] : 'show'; // hide|show
			$type = in_array( $type, array( 'hide', 'show' ), true ) ? $type : 'show';

			/**
			 * Interpret:
			 * - type=hide => if match => remove
			 * - type=show => if NOT match => remove
			 */
			if ( 'hide' === $type && true === $is_match ) {
				$remove_ids[] = $item_id;
			}

			if ( 'show' === $type && false === $is_match ) {
				$remove_ids[] = $item_id;
			}
		}

		return $this->remove_items_with_children( $items, $remove_ids );
	}

	/**
	 * Remove selected items and their children (expected behavior).
	 *
	 * @param array $items
	 * @param array $remove_ids
	 *
	 * @return array
	 */
	protected function remove_items_with_children( $items, $remove_ids ) {

		if ( empty( $remove_ids ) ) {
			return $items;
		}

		$remove_map = array_fill_keys(
			array_map( 'absint', $remove_ids ),
			true
		);

		$parents = array();

		foreach ( $items as $item ) {
			$id     = isset( $item->ID ) ? absint( $item->ID ) : 0;
			$parent = isset( $item->menu_item_parent ) ? absint( $item->menu_item_parent ) : 0;

			if ( $id ) {
				$parents[ $id ] = $parent;
			}
		}

		// Cascade: if parent is removed => remove children.
		$changed = true;

		while ( $changed ) {
			$changed = false;

			foreach ( $parents as $id => $parent ) {
				if ( $parent && isset( $remove_map[ $parent ] ) && ! isset( $remove_map[ $id ] ) ) {
					$remove_map[ $id ] = true;
					$changed           = true;
				}
			}
		}

		$result = array();

		foreach ( $items as $item ) {
			$id = isset( $item->ID ) ? absint( $item->ID ) : 0;

			if ( $id && isset( $remove_map[ $id ] ) ) {
				continue;
			}

			$result[] = $item;
		}

		return $result;
	}
}

add_action(
	'init',
	function () {
		new Dynamic_Visibility();
	},
	20
);
