<?php
/**
 * Page block engine.
 *
 * @package Polanger_Shield
 */

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

class Polanger_Shield_Page_Blocker {

  /**
   * Active page-block rules indexed by menu slug for the current request.
   *
   * @var array<string, array>
   */
  private $menu_rule_cache = array();

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

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

    return self::$instance;
  }

  /**
   * Register hooks.
   *
   * @return void
   */
  public function register_hooks() {
    add_action( 'admin_init', array( $this, 'maybe_block_current_page' ), 1 );
    add_action( 'admin_menu', array( $this, 'maybe_hide_blocked_menu_items' ), 99999 );
  }

  /**
   * Block page when matching page-block rule targets current user.
   *
   * @return void
   */
  public function maybe_block_current_page() {
    if ( wp_doing_ajax() ) {
      return;
    }

    if ( Polanger_Shield_Safe_Mode::is_active() ) {
      return;
    }

    $context = Polanger_Shield_Context::current();
    $rules = Polanger_Shield_Rules::get_active_rules_for_context( 'page_block', $context );
    if ( empty( $rules ) ) {
      return;
    }

    $user_id = get_current_user_id();
    foreach ( $rules as $rule ) {
      if ( ! Polanger_Shield_Permissions::is_user_targeted_by_rule( $rule, $user_id ) ) {
        continue;
      }

      if ( empty( $rule->block_url ) ) {
        continue;
      }

      Polanger_Shield_Activity::log_page_blocked(
        absint( $rule->id ),
        $user_id,
        array(
          'rule_type' => 'page_block',
          'screen_id' => $context['screen_id'],
          'page_slug' => $context['page_slug'],
        )
      );

      $dashboard_url = admin_url( 'index.php' );
      $message = sprintf(
        /* translators: %s dashboard URL */
        __( 'You do not have permission to access this admin page. <br /><br /><a class="button button-primary" href="%s">Go to Dashboard</a>', 'polanger-admin-menu-manager' ),
        esc_url( $dashboard_url )
      );

      wp_die(
        wp_kses_post( $message ),
        esc_html__( 'Access Denied', 'polanger-admin-menu-manager' ),
        array(
          'response' => 403,
        )
      );
    }
  }

  /**
   * Hide blocked pages from admin menu for targeted users.
   *
   * @return void
   */
  public function maybe_hide_blocked_menu_items() {
    if ( wp_doing_ajax() ) {
      return;
    }

    if ( Polanger_Shield_Safe_Mode::is_active() ) {
      return;
    }

    $user_id = get_current_user_id();
    if ( ! $user_id ) {
      return;
    }

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

    global $menu, $submenu;

    foreach ( $rules as $rule ) {
      if ( ! Polanger_Shield_Permissions::is_user_targeted_by_rule( $rule, $user_id ) ) {
        continue;
      }

      $candidate_slugs = $this->get_menu_slug_candidates_for_rule( $rule );
      if ( empty( $candidate_slugs ) ) {
        continue;
      }

      foreach ( $candidate_slugs as $candidate_slug ) {
        remove_menu_page( $candidate_slug );
        remove_submenu_page( 'polanger-dashboard', $candidate_slug );
      }

      if ( is_array( $menu ) ) {
        foreach ( $menu as $menu_index => $menu_item ) {
          $menu_slug = isset( $menu_item[2] ) ? (string) $menu_item[2] : '';
          if ( $this->menu_slug_matches_candidates( $menu_slug, $candidate_slugs ) ) {
            unset( $menu[ $menu_index ] );
          }
        }
      }

      if ( is_array( $submenu ) ) {
        foreach ( $submenu as $parent_slug => $items ) {
          if ( ! is_array( $items ) ) {
            continue;
          }
          foreach ( $items as $sub_index => $sub_item ) {
            $submenu_slug = isset( $sub_item[2] ) ? (string) $sub_item[2] : '';
            if ( $this->menu_slug_matches_candidates( $submenu_slug, $candidate_slugs ) ) {
              unset( $submenu[ $parent_slug ][ $sub_index ] );
            }
          }
        }
      }
    }
  }

  /**
   * Return active Shield page rules that map to a Menu Manager slug.
   *
   * This deliberately reuses the exact candidate matcher used by runtime menu
   * hiding so informational integrations never drift from enforced behavior.
   *
   * @param string $menu_slug WordPress admin menu or submenu slug.
   * @return array
   */
  public function get_active_rules_for_menu_slug( $menu_slug ) {
    $menu_slug = sanitize_text_field( (string) $menu_slug );
    if ( '' === $menu_slug ) {
      return array();
    }

    if ( isset( $this->menu_rule_cache[ $menu_slug ] ) ) {
      return $this->menu_rule_cache[ $menu_slug ];
    }

    $matched_rules = array();
    $rules         = Polanger_Shield_Rules::get_active_rules( 'page_block' );

    foreach ( $rules as $rule ) {
      $candidate_slugs = $this->get_menu_slug_candidates_for_rule( $rule );
      if ( $this->menu_slug_matches_candidates( $menu_slug, $candidate_slugs ) ) {
        $matched_rules[] = $rule;
      }
    }

    $this->menu_rule_cache[ $menu_slug ] = $matched_rules;

    return $this->menu_rule_cache[ $menu_slug ];
  }

  /**
   * Build possible admin menu slugs for one rule.
   *
   * @param object $rule Rule record.
   * @return string[]
   */
  private function get_menu_slug_candidates_for_rule( $rule ) {
    $candidates = array();

    $page_slug = isset( $rule->page_slug ) ? sanitize_text_field( (string) $rule->page_slug ) : '';
    if ( '' !== $page_slug ) {
      $candidates[] = $page_slug;
      $candidates[] = 'admin.php?page=' . $page_slug;
    }

    $request_uri = isset( $rule->request_uri ) ? sanitize_text_field( (string) $rule->request_uri ) : '';
    if ( '' !== $request_uri ) {
      $relative = $this->normalize_admin_slug( $request_uri );
      if ( '' !== $relative ) {
        $candidates[] = $relative;

        $parts = wp_parse_url( $request_uri );
        $path  = isset( $parts['path'] ) ? (string) $parts['path'] : '';
        $base  = '' !== $path ? wp_basename( $path ) : '';

        if ( '' !== $base ) {
          $candidates[] = $base;
        }
      }
    }

    $screen_id = isset( $rule->screen_id ) ? sanitize_text_field( (string) $rule->screen_id ) : '';
    if ( '' !== $screen_id && false === strpos( $screen_id, '.' ) ) {
      $candidates[] = $screen_id . '.php';
    }

    $candidates = array_filter( array_map( 'strval', $candidates ) );
    $candidates = array_values( array_unique( $candidates ) );

    return $candidates;
  }

  /**
   * Compare one menu slug against the rule candidates.
   *
   * @param string   $menu_slug        Menu slug from WP menu globals.
   * @param string[] $candidate_slugs Candidate slugs for the rule.
   * @return bool
   */
  private function menu_slug_matches_candidates( $menu_slug, $candidate_slugs ) {
    $menu_slug = sanitize_text_field( (string) $menu_slug );
    if ( '' === $menu_slug || empty( $candidate_slugs ) ) {
      return false;
    }

    $normalized_menu_slug = $this->normalize_admin_slug( $menu_slug );

    foreach ( $candidate_slugs as $candidate_slug ) {
      $candidate_slug = sanitize_text_field( (string) $candidate_slug );
      if ( '' === $candidate_slug ) {
        continue;
      }

      if ( $menu_slug === $candidate_slug || $normalized_menu_slug === $candidate_slug ) {
        return true;
      }

      $normalized_candidate = $this->normalize_admin_slug( $candidate_slug );
      if ( '' !== $normalized_candidate && $normalized_menu_slug === $normalized_candidate ) {
        return true;
      }
    }

    return false;
  }

  /**
   * Normalize an admin slug or request URI into a relative admin target.
   *
   * Examples:
   * - /site/wp-admin/edit.php?post_type=page => edit.php?post_type=page
   * - http://example.com/site/wp-admin/plugins.php => plugins.php
   * - admin.php?page=my-plugin => admin.php?page=my-plugin
   *
   * @param string $value Raw slug or URI.
   * @return string
   */
  private function normalize_admin_slug( $value ) {
    $value = trim( (string) $value );
    if ( '' === $value ) {
      return '';
    }

    $parts = wp_parse_url( $value );

    if ( false === $parts ) {
      return sanitize_text_field( ltrim( $value, '/' ) );
    }

    $path  = isset( $parts['path'] ) ? (string) $parts['path'] : '';
    $query = isset( $parts['query'] ) ? (string) $parts['query'] : '';

    if ( '' === $path && '' === $query ) {
      return sanitize_text_field( ltrim( $value, '/' ) );
    }

    if ( '' !== $path ) {
      $path = preg_replace( '#^.*?/wp-admin/#', '', $path );
      $path = ltrim( (string) $path, '/' );
    }

    if ( '' === $query ) {
      return sanitize_text_field( $path );
    }

    parse_str( $query, $query_args );
    foreach ( array( '_wpnonce', '_wp_http_referer', 'updated', 'settings-updated', 'message', 'paged', 'polanger_shield_safe', 'polanger_shield_test_rule' ) as $key ) {
      unset( $query_args[ $key ] );
    }

    if ( empty( $query_args ) ) {
      return sanitize_text_field( $path );
    }

    ksort( $query_args );
    return sanitize_text_field( $path . '?' . http_build_query( $query_args, '', '&' ) );
  }
}
