<?php
/**
 * Read-only Shield indicators for the core Menu Manager screen.
 *
 * @package Polanger_Shield
 */

if ( ! defined( 'ABSPATH' ) ) {
  exit;
}

class Polanger_Shield_Menu_Manager_Integration {

  /**
   * Singleton instance.
   *
   * @var Polanger_Shield_Menu_Manager_Integration|null
   */
  private static $instance = null;

  /**
   * Dialog content collected while Menu Manager rows are rendered.
   *
   * @var array<string,array<string,mixed>>
   */
  private $dialog_sources = array();

  /**
   * Cached labels for every user referenced by active page-block rules.
   *
   * @var array<int,string>|null
   */
  private $user_labels = null;

  /**
   * Per-request indicator counter.
   *
   * @var int
   */
  private $indicator_counter = 0;

  /**
   * Get singleton instance.
   *
   * @return Polanger_Shield_Menu_Manager_Integration
   */
  public static function instance() {
    if ( null === self::$instance ) {
      self::$instance = new self();
    }

    return self::$instance;
  }

  /**
   * Register integration hooks.
   *
   * @return void
   */
  public function register_hooks() {
    add_action( 'pdt_menu_manager_item_label_after', array( $this, 'render_menu_indicator' ), 10, 3 );
    add_action( 'pdt_menu_manager_submenu_label_after', array( $this, 'render_submenu_indicator' ), 10, 4 );
    add_action( 'pdt_menu_manager_after_form', array( $this, 'render_dialog' ) );
    add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
  }

  /**
   * Enqueue assets only when Menu Manager can contain Shield indicators.
   *
   * @return void
   */
  public function enqueue_assets() {
    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only page routing check.
    $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';

    if ( 'polanger-dashboard' !== $page || ! $this->can_view_integration() ) {
      return;
    }

    if ( empty( Polanger_Shield_Rules::get_active_rules( 'page_block' ) ) ) {
      return;
    }

    wp_enqueue_style(
      'polanger-shield-menu-manager',
      POLANGER_SHIELD_URL . 'assets/css/shield-menu-manager.css',
      array( 'pdt-admin-style' ),
      POLANGER_SHIELD_VERSION
    );

    wp_enqueue_script(
      'polanger-shield-menu-manager',
      POLANGER_SHIELD_URL . 'assets/js/shield-menu-manager.js',
      array(),
      POLANGER_SHIELD_VERSION,
      true
    );
  }

  /**
   * Render an indicator for a main menu item.
   *
   * @param string $menu_slug Menu slug.
   * @param mixed  $item      Captured menu data.
   * @param string $label     Plain-text menu label.
   * @return void
   */
  public function render_menu_indicator( $menu_slug, $item, $label ) {
    unset( $item );
    $this->render_indicator( $menu_slug, $label );
  }

  /**
   * Render an indicator for a submenu item.
   *
   * @param string $parent_slug Parent menu slug.
   * @param string $menu_slug   Submenu slug.
   * @param mixed  $item        Captured submenu data.
   * @param string $label       Plain-text submenu label.
   * @return void
   */
  public function render_submenu_indicator( $parent_slug, $menu_slug, $item, $label ) {
    unset( $parent_slug, $item );
    $this->render_indicator( $menu_slug, $label );
  }

  /**
   * Render collected dialog sources and the shared accessible modal.
   *
   * @return void
   */
  public function render_dialog() {
    if ( empty( $this->dialog_sources ) || ! $this->can_view_integration() ) {
      return;
    }

    foreach ( $this->dialog_sources as $source_id => $details ) :
      ?>
      <div id="<?php echo esc_attr( $source_id ); ?>" class="polanger-shield-menu-dialog-source" hidden>
        <p class="polanger-shield-menu-dialog-intro">
          <?php esc_html_e( 'This menu item is already controlled by one or more active Shield page rules. You do not need to create the same restriction again in Menu Manager.', 'polanger-admin-menu-manager' ); ?>
        </p>

        <section class="polanger-shield-menu-dialog-section">
          <h3><span class="dashicons dashicons-hidden" aria-hidden="true"></span><?php esc_html_e( 'Hidden by Shield', 'polanger-admin-menu-manager' ); ?></h3>
          <?php $this->render_user_list( $details['hidden_users'] ); ?>
        </section>

        <section class="polanger-shield-menu-dialog-section">
          <h3><span class="dashicons dashicons-lock" aria-hidden="true"></span><?php esc_html_e( 'URL access blocked', 'polanger-admin-menu-manager' ); ?></h3>
          <?php if ( ! empty( $details['blocked_users'] ) ) : ?>
            <?php $this->render_user_list( $details['blocked_users'] ); ?>
          <?php else : ?>
            <p class="polanger-shield-menu-dialog-note">
              <?php esc_html_e( 'Direct URL access is not blocked by Shield for this item.', 'polanger-admin-menu-manager' ); ?>
            </p>
          <?php endif; ?>
        </section>

        <div class="polanger-shield-menu-dialog-actions">
          <a class="button button-primary" href="<?php echo esc_url( $details['rules_url'] ); ?>">
            <?php esc_html_e( 'Review Shield Rules', 'polanger-admin-menu-manager' ); ?>
          </a>
        </div>
      </div>
      <?php
    endforeach;
    ?>
    <div id="polanger-shield-menu-info-modal" class="polanger-shield-menu-modal" aria-hidden="true" hidden>
      <div class="polanger-shield-menu-modal__backdrop" data-polanger-shield-menu-close></div>
      <div class="polanger-shield-menu-modal__dialog" role="dialog" aria-modal="true" aria-labelledby="polanger-shield-menu-modal-title" tabindex="-1">
        <header class="polanger-shield-menu-modal__header">
          <div class="polanger-shield-menu-modal__heading">
            <span class="dashicons dashicons-shield" aria-hidden="true"></span>
            <h2 id="polanger-shield-menu-modal-title"></h2>
          </div>
          <button type="button" class="polanger-shield-menu-modal__close" data-polanger-shield-menu-close aria-label="<?php esc_attr_e( 'Close', 'polanger-admin-menu-manager' ); ?>">
            <span class="dashicons dashicons-no-alt" aria-hidden="true"></span>
          </button>
        </header>
        <div class="polanger-shield-menu-modal__body"></div>
      </div>
    </div>
    <?php
  }

  /**
   * Render one row indicator and collect its modal content.
   *
   * @param string $menu_slug Menu or submenu slug.
   * @param string $label     Plain-text menu label.
   * @return void
   */
  private function render_indicator( $menu_slug, $label ) {
    if ( ! $this->can_view_integration() ) {
      return;
    }

    $rules = Polanger_Shield_Page_Blocker::instance()->get_active_rules_for_menu_slug( $menu_slug );
    if ( empty( $rules ) ) {
      return;
    }

    $hidden_user_ids  = array();
    $blocked_user_ids = array();
    $rule_ids         = array();

    foreach ( $rules as $rule ) {
      $target_user_ids = $this->get_rule_target_user_ids( $rule );
      if ( empty( $target_user_ids ) ) {
        continue;
      }

      $hidden_user_ids = array_merge( $hidden_user_ids, $target_user_ids );
      if ( ! empty( $rule->block_url ) ) {
        $blocked_user_ids = array_merge( $blocked_user_ids, $target_user_ids );
      }

      if ( ! empty( $rule->id ) ) {
        $rule_ids[] = absint( $rule->id );
      }
    }

    $hidden_user_ids = $this->normalize_user_ids( $hidden_user_ids );
    if ( empty( $hidden_user_ids ) ) {
      return;
    }

    $blocked_user_ids = $this->normalize_user_ids( $blocked_user_ids );
    $rule_ids         = array_values( array_unique( array_filter( array_map( 'absint', $rule_ids ) ) ) );
    $label            = sanitize_text_field( (string) $label );

    ++$this->indicator_counter;
    $source_id = 'polanger-shield-menu-source-' . $this->indicator_counter;
    $title     = sprintf(
      /* translators: %s menu item label */
      __( 'Shield protection details: %s', 'polanger-admin-menu-manager' ),
      $label
    );

    $rules_url = admin_url( 'admin.php?page=polanger-shield' );
    if ( ! empty( $rule_ids ) ) {
      $rules_url = add_query_arg( 'rule_id', $rule_ids[0], $rules_url );
    }

    $this->dialog_sources[ $source_id ] = array(
      'hidden_users'  => $this->get_user_labels( $hidden_user_ids ),
      'blocked_users' => $this->get_user_labels( $blocked_user_ids ),
      'rules_url'     => $rules_url,
    );
    ?>
    <button
      type="button"
      class="polanger-shield-menu-indicator"
      aria-expanded="false"
      aria-controls="polanger-shield-menu-info-modal"
      data-polanger-shield-menu-source="<?php echo esc_attr( $source_id ); ?>"
      data-polanger-shield-menu-title="<?php echo esc_attr( $title ); ?>"
      title="<?php esc_attr_e( 'View Shield protection details', 'polanger-admin-menu-manager' ); ?>"
    >
      <span class="dashicons dashicons-info-outline" aria-hidden="true"></span>
      <span class="screen-reader-text"><?php esc_html_e( 'View Shield protection details', 'polanger-admin-menu-manager' ); ?></span>
    </button>
    <?php
  }

  /**
   * Render user chips.
   *
   * @param array $labels User labels.
   * @return void
   */
  private function render_user_list( $labels ) {
    if ( empty( $labels ) ) {
      return;
    }
    ?>
    <ul class="polanger-shield-menu-user-list">
      <?php foreach ( $labels as $label ) : ?>
        <li><span class="dashicons dashicons-admin-users" aria-hidden="true"></span><?php echo esc_html( $label ); ?></li>
      <?php endforeach; ?>
    </ul>
    <?php
  }

  /**
   * Return normalized target IDs from one Shield rule.
   *
   * @param object $rule Shield rule.
   * @return array<int>
   */
  private function get_rule_target_user_ids( $rule ) {
    if ( ! is_object( $rule ) || ! isset( $rule->target_user_ids ) ) {
      return array();
    }

    $user_ids = json_decode( (string) $rule->target_user_ids, true );

    return $this->normalize_user_ids( is_array( $user_ids ) ? $user_ids : array() );
  }

  /**
   * Normalize a user ID list.
   *
   * @param array $user_ids Raw IDs.
   * @return array<int>
   */
  private function normalize_user_ids( $user_ids ) {
    $ids = array();

    foreach ( (array) $user_ids as $user_id ) {
      $normalized = $this->normalize_user_id( $user_id );
      if ( $normalized ) {
        $ids[] = $normalized;
      }
    }

    return array_values( array_unique( $ids ) );
  }

  /**
   * Normalize a mixed user value into a user ID.
   *
   * @param mixed $user User ID, WP_User, stdClass, or array-like value.
   * @return int
   */
  private function normalize_user_id( $user ) {
    if ( is_numeric( $user ) ) {
      return absint( $user );
    }

    if ( $user instanceof WP_User ) {
      return absint( $user->ID );
    }

    if ( is_object( $user ) ) {
      foreach ( array( 'ID', 'id', 'user_id' ) as $property ) {
        if ( isset( $user->{$property} ) && is_numeric( $user->{$property} ) ) {
          return absint( $user->{$property} );
        }
      }
    }

    if ( is_array( $user ) ) {
      foreach ( array( 'ID', 'id', 'user_id' ) as $key ) {
        if ( isset( $user[ $key ] ) && is_numeric( $user[ $key ] ) ) {
          return absint( $user[ $key ] );
        }
      }
    }

    return 0;
  }

  /**
   * Resolve display labels for target users with one cached user query.
   *
   * @param array $user_ids User IDs to format.
   * @return array<string>
   */
  private function get_user_labels( $user_ids ) {
    if ( null === $this->user_labels ) {
      $this->user_labels = array();
      $all_user_ids      = array();

      foreach ( Polanger_Shield_Rules::get_active_rules( 'page_block' ) as $rule ) {
        $all_user_ids = array_merge( $all_user_ids, $this->get_rule_target_user_ids( $rule ) );
      }

      $all_user_ids = $this->normalize_user_ids( $all_user_ids );
      if ( ! empty( $all_user_ids ) ) {
        $users = get_users(
          array(
            'include' => $all_user_ids,
            'orderby' => 'display_name',
            'order'   => 'ASC',
          )
        );

        foreach ( $users as $user ) {
          $display_name = '' !== trim( (string) $user->display_name ) ? $user->display_name : $user->user_login;
          $this->user_labels[ (int) $user->ID ] = sprintf( '%1$s (%2$s)', $display_name, $user->user_login );
        }
      }
    }

    $labels = array();
    foreach ( $this->normalize_user_ids( $user_ids ) as $user_id ) {
      $labels[] = isset( $this->user_labels[ $user_id ] ) ? $this->user_labels[ $user_id ] : '#' . $user_id;
    }

    return $labels;
  }

  /**
   * Whether the current administrator may inspect Shield rule details.
   *
   * @return bool
   */
  private function can_view_integration() {
    return Polanger_Shield_Permissions::current_user_can_manage_shield( true );
  }
}
