<?php
/**
 * Main Nisje Class.
 *
 * @package Nisje
 */

namespace Dekode\Nisje;

defined( 'ABSPATH' ) || die( 'Shame on you' );

/**
 * Nisje Core Class.
 */
class Core {

	/**
	 * Settings API.
	 *
	 * @var object
	 */
	public $settings;

	/**
	 * Plugin path
	 *
	 * @var string
	 */
	public $path;

	/**
	 * Plugin dir
	 *
	 * @var string
	 */
	public $dir;

	/**
	 * Versions.
	 *
	 * @var array
	 */
	public $versions;

	/**
	 * Components.
	 *
	 * @var array
	 */
	public $components;

	/**
	 * A dummy constructor
	 */
	public function __construct() {
		// Do nothing.
	}

	/**
	 * Main Instance
	 *
	 * @static object $instance
	 * @return Nisje|null The object.
	 */
	public static function instance() {
		// Store the instance locally to avoid private static replication.
		static $instance = null;

		// Only run these methods if they haven't been run previously.
		if ( null === $instance ) {
			$instance = new Core();

			$instance->setup_globals();
			$instance->includes();
			$instance->setup_actions();
		}

		// Always return the instance.
		return $instance;
	}

	/**
	 * Global variables
	 */
	private function setup_globals() {
		global $wpdb;

		$this->dir  = plugin_dir_url( __FILE__ );
		$this->path = plugin_dir_path( __FILE__ );

		$this->versions = [
			'plugin_version' => NISJE_CORE_VERSION,
			'db_version'     => NISJE_CORE_DB_VERSION,
		];

		$wpdb->di_event_attending = $wpdb->prefix . 'di_event_attending';
	}

	/**
	 * Include required files
	 */
	private function includes() {
		nisje_include( 'includes/global-update-functions.php' );
		nisje_include( 'includes/global-rest-functions.php' );

		// Setup and initialize settings class.
		nisje_include( 'includes/class-settings.php' );
		$this->settings = Settings::instance();

		// Normalize/optimize the installation.
		nisje_include( 'includes/resets/resets.php' );

		// Include rest helper.
		nisje_include( 'includes/helpers/class-wp-api-helper.php' );

		// Functionality that eventually will die.
		nisje_include( 'includes/deprecated.php' );
	}

	/**
	 * Set up the default hooks and actions
	 */
	private function setup_actions() {
		if ( is_admin() ) {
			// Admin requirements and notifications.
			add_action( 'admin_init', [ $this, 'requirements' ] );
			add_action( 'admin_init', [ $this, 'updates' ] );
		}

		// Load text domain on plugins_loaded.
		add_action( 'plugins_loaded', [ $this, 'load_textdomain' ] );

		// Add custom hook to help sub modules.
		add_action( 'plugins_loaded', [ $this, 'include_hook' ], 11 );

		// Admin restriction.
		add_action( 'admin_init', [ $this, 'restrict_admin' ], 1 );

		// Core functionality.
		add_action( 'nisje_include', [ $this, 'core_functionality' ], 100 );

		// Include components.
		add_action( 'nisje_include', [ $this, 'components' ], 100 );

		// Register taxonomies, post_types and metadata.
		add_action( 'init', [ $this, 'register_content_types' ] );

		// Register ACF data.
		add_action( 'plugins_loaded', [ $this, 'register_acf' ], 100 );

		// Register Rest handlers.
		add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );

		// Close down API. Users must be logged in to use it!
		add_filter( 'rest_authentication_errors', [ $this, 'filter_routes' ] );

		// Addional rest headers.
		add_action( 'rest_pre_serve_request', [ $this, 'additional_rest_headers' ] );

		// Status header.
		add_action( 'pre_handle_404', [ $this, 'force_200_on_request' ], 10, 2 );
		add_action( 'status_header', [ $this, 'force_200_on_status_request' ], 10, 4 );
		add_action( 'parse_query', [ $this, 'parse_wp_query' ] );

		// Register Theme Functionality.
		add_filter( 'allowed_block_types', [ $this, 'allowed_block_types' ], 10, 2 );
		add_action( 'after_setup_theme', [ $this, 'register_theme_functionality' ] );
	}

	/**
	 * Validate whether the requirements for this plugin are available.
	 */
	public function requirements() {
		if ( ! is_plugin_active( 'buddypress/bp-loader.php' ) || ! is_plugin_active( 'advanced-custom-fields-pro/acf.php' ) ) {
			add_action( 'admin_notices', function() {
				?>
				<div class="error">
					<p>
						<strong><?php echo esc_html( nisje_get_setting( 'name', 'core' ) ); ?>:</strong>
						<?php esc_html_e( 'BuddyPress or ACF Pro is missing. Please activate BuddyPress and ACF before you activate this plugin.', 'nisje' ); ?>
					</p>
				</div>
				<?php
			} );

			deactivate_plugins( plugin_basename( __FILE__ ) );
		}
	}

	/**
	 * Check if plugin needs re-activation.
	 */
	public function updates() {
		do_action( 'nisje_updates' );

		$current_plugin_version = nisje_get_version();
		$installed_version      = get_option( 'nisje_version' );

		// New install.
		if ( ! $installed_version ) {
			update_option( 'nisje_version', $current_plugin_version );

			nisje_activation();

			return;
		}

		// Do nothing if $installed_version is >= $plugin_version.
		if ( version_compare( $installed_version, $current_plugin_version, '>=' ) ) {
			return;
		} else {
			nisje_activation();

			add_action( 'admin_notices', function () {
				?>
				<div class="notice">
					<p>
						<strong><?php echo esc_html( nisje_get_setting( 'name', 'core' ) ); ?>:</strong>
						<?php esc_html_e( 'Plugin updated!', 'nisje' ); ?>
					</p>
				</div>
				<?php
			} );
		}
	}

	/**
	 * Nisje Include Hook
	 */
	public function include_hook() {
		if ( function_exists( '\buddypress' ) ) {
			do_action( 'nisje_include' );
		}
	}

	/**
	 * Load Plugin Text Domain
	 */
	public function load_textdomain() {
		load_plugin_textdomain( 'nisje', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
	}

	/**
	 * Restrict Backend
	 */
	public function restrict_admin() {
		global $pagenow;

		if ( in_array( $pagenow, [ 'post.php', 'post-new.php' ], true ) ) {
			if ( 'post-new.php' === $pagenow ) {
				$post_type = filter_input( INPUT_GET, 'post_type', FILTER_SANITIZE_STRING );
			} else {
				$var_post    = filter_input( INPUT_GET, 'post', FILTER_SANITIZE_NUMBER_INT );
				$var_post_id = filter_input( INPUT_POST, 'post_ID', FILTER_SANITIZE_NUMBER_INT );

				if ( $var_post ) {
					$post_id = $var_post;
				} elseif ( $var_post_id ) {
					$post_id = $var_post_id;
				} else {
					$post_id = 0;
				}

				if ( $post_id ) {
					$post = get_post( $post_id );
				}

				if ( $post ) {
					$post_type        = $post->post_type;
					$post_type_object = get_post_type_object( $post_type );
				}
			}

			if ( isset( $post_type ) ) {
				/**
				 * Filters allowed post types.
				 *
				 * @param array $post_types Allowd post types.
				 */
				$post_types = apply_filters( 'nisje_gutenberg_group_post_types', [] );
				if ( in_array( $post_type, $post_types, true ) ) {
					$group_id = filter_input( INPUT_GET, 'group_id', FILTER_SANITIZE_NUMBER_INT );
					if ( ! $group_id ) {
						wp_die( esc_html__( 'Missing group ID', 'nisje' ), esc_html__( 'Please provide group ID.', 'nisje' ), 400 );
					}
				}
			}
		} else {
			if ( is_admin() && ! current_user_can( 'moderate_comments' ) && ! ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
				wp_safe_redirect( home_url( '/' ) );

				exit;
			}
		}
	}

	/**
	 * Include core functionality
	 */
	public function core_functionality() {
		nisje_include( 'includes/core/comments/comments.php' );
		nisje_include( 'includes/core/groups/groups.php' );
		nisje_include( 'includes/core/mentions/mentions.php' );
		nisje_include( 'includes/core/notifications/notifications.php' );
		nisje_include( 'includes/core/notifications/email-notifications.php' );
		nisje_include( 'includes/core/reactions/reactions.php' );
		nisje_include( 'includes/core/user/user.php' );
		nisje_include( 'includes/core/tracking/tracking.php' );
	}

	/**
	 * Include Components
	 */
	public function components() {
		nisje_include( 'includes/components/class-component.php' );

		$this->include_core_components();

		do_action( 'nisje_register_components' );

		$registered_components = apply_filters( 'nisje_components', [] );

		foreach ( (array) $registered_components as $component ) {
			if ( ! ( $component instanceof \Dekode\Nisje\Components\Component ) ) {
				$component = new $component();
			}

			// Magically do includes.
			if ( method_exists( $component, 'includes' ) ) {
				$component->includes();
			}

			// Magically setup globals.
			if ( method_exists( $component, 'setup_globals' ) ) {
				$component->setup_globals();
			}

			// Magically setup filters.
			if ( method_exists( $component, 'setup_filters' ) ) {
				$component->setup_filters();
			}

			$this->components[ $component->name ] = $component;
		}

		do_action( 'nisje_components_registered' );
	}

	/**
	 * Include Core Components
	 */
	private function include_core_components() {
		nisje_include( 'includes/components/attachments/attachments.php' );
		nisje_include( 'includes/components/avatars/avatars.php' );
		nisje_include( 'includes/components/customizer/customizer.php' );
		nisje_include( 'includes/components/mail-notifications/mail-notifications.php' );
		nisje_include( 'includes/components/feed/feed.php' );
		nisje_include( 'includes/components/group-types/group-types.php' );
		nisje_include( 'includes/components/groups/groups.php' );
		nisje_include( 'includes/components/member-types/member-types.php' );
		nisje_include( 'includes/components/profile-layouts/profile-layouts.php' );
		nisje_include( 'includes/components/reaction/reaction.php' );
		nisje_include( 'includes/components/scraper/scraper.php' );
		nisje_include( 'includes/components/settings/settings.php' );
		nisje_include( 'includes/components/xprofile/xprofile.php' );
	}

	/**
	 * Register Content Types
	 */
	public function register_content_types() {
		do_action( 'nisje_register_taxonomies', $this->versions['plugin_version'] );
		do_action( 'nisje_register_post_types', $this->versions['plugin_version'] );
		do_action( 'nisje_register_metadata', $this->versions['plugin_version'] );
		do_action( 'nisje_register_content_types', $this->versions['plugin_version'] );

		nisje_include( 'includes/taxonomies/taxonomy-associated.php' );
		nisje_include( 'includes/taxonomies/taxonomy-access.php' );
		nisje_include( 'includes/taxonomies/taxonomy-map-group-types.php' );
		nisje_include( 'includes/taxonomies/taxonomy-tags.php' );
	}

	/**
	 * Register ACF Functionality
	 */
	public function register_acf() {
		// Core ACF.
		add_action( 'acf/include_fields', [ $this, 'register_acf_fields' ] );
		add_action( 'acf/include_field_types', [ $this, 'register_acf_field_types' ] );

		// 3rd party ACF.
		do_action( 'nisje_register_acf', $this->versions['plugin_version'] );
	}

	/**
	 * Register acf fields
	 */
	public function register_acf_fields() {
		do_action( 'nisje_register_acf_fields', $this->versions['plugin_version'] );
	}

	/**
	 * Register acf fields
	 */
	public function register_acf_field_types() {
		nisje_include( 'includes/acf/fields/class-acf-field-bp-groups.php' );
		nisje_include( 'includes/acf/fields/class-acf-field-bp-member-types.php' );
		nisje_include( 'includes/acf/fields/class-acf-field-bp-profile-fields.php' );
		nisje_include( 'includes/acf/fields/class-acf-field-bp-user-groups.php' );

		// 3rd party ACF fields
		do_action( 'nisje_register_acf_field_types', $this->versions['plugin_version'] );
	}

	/**
	 * Register Rest routes
	 */
	public function register_rest_routes() {
		global $wp_query;

		if ( ! class_exists( 'WP_REST_Controller' ) ) {
			return;
		}

		// Include global/shared rest handlers.
		nisje_include( 'includes/rest-handlers/class-base-group-post-controller.php' );
		nisje_include( 'includes/rest-handlers/class-base-archive-post-controller.php' );

		// Extend BuddyPress groups.
		nisje_include( 'includes/rest-handlers/extend-buddypress-groups.php' );
		// Extend group post-types with shared data.
		nisje_include( 'includes/rest-handlers/extend-group-post-types.php' );
		// Extend post-types with shared data.
		nisje_include( 'includes/rest-handlers/extend-post-types.php' );

		do_action( 'nisje_register_rest_routes' );
	}

	/**
	 * Only allow certain REST endpoints.
	 *
	 * @param WP_Error|null|bool $result WP_Error if authentication error, null if authentication method wasn't used, true if authentication succeeded.
	 * @return WP_Error|null|bool WP_Error if authentication error, null if authentication method wasn't used, true if authentication succeeded.
	 */
	public function filter_routes( $result ) {
		global $wp;

		if ( is_user_logged_in() ) {
			return $result;
		}

		$current_route = $wp->query_vars['rest_route'];
		$current_route = ( empty( $current_route ) || '/' === $current_route ) ? $current_route : untrailingslashit( $current_route );

		$open_routes = [
			'/nisje/v1/settings',
			'/dekode-intranet/v1/users',
			'/wp/v2/users',
			'/jwt-auth/v1',
			'/jwt-auth/v1/token',
			'/jwt-auth/v1/token/validate',
		];

		$open_routes = apply_filters( 'nisje_filter_routes', $open_routes );

		if ( ! in_array( $current_route, $open_routes, true ) ) {
			return new \WP_Error( 'rest_not_logged_in', esc_html__( 'You are not currently logged in.', 'nisje' ), [
				'status' => 401,
			] );
		}

		return $result;
	}

	/**
	 * Add 'Platform -> nisje' header to all responses.
	 */
	public function additional_rest_headers() {
		header( 'Platform: nisje' );

		do_action( 'nisje_additional_rest_headers' );
	}

	/**
	 * Send 200 for all requests
	 *
	 * @param bool      $retval   Retval.
	 * @param \WP_Query $wp_query WP_Query.
	 *
	 * @return bool
	 */
	public function force_200_on_request( bool $retval, \WP_Query $wp_query ) : bool {
		status_header( 200 );

		return false;
	}

	/**
	 * Send 200 for all requests
	 *
	 * @param string $status_header HTTP status header.
	 * @param int    $code          HTTP status code.
	 * @param string $description   Description for the status code.
	 * @param string $protocol      Server protocol.
	 *
	 * @return string $status_header Status header.
	 */
	public function force_200_on_status_request( $status_header, $code, $description, $protocol ) : string {
		if ( 'HTTP/1.0 404 Not Found' === $status_header ) {
			$status_header = 'HTTP/1.0 200 OK';
		}

		return $status_header;
	}

	/**
	 * Avoid setting 404. Probably the winner of this years WP hackaton :(
	 *
	 * @param \WP_Query $wp_query WP_Query.
	 */
	public function parse_wp_query( \WP_Query $wp_query ) {
		if ( $wp_query->is_main_query() && ! $wp_query->is_admin() && $wp_query->is_404() ) {
			$wp_query->is_404    = false;
			$wp_query->is_single = true;

			unset( $wp_query->query['error'] );

			$wp_query->query_vars['error'] = '';
		}
	}

	/**
	 * Allowed Blocks
	 *
	 * @param bool|array $allowed_block_types Allowed block Types.
	 * @param \WP_Post   $post                Post object.
	 *
	 * @return bool|array $allowed_block_types Allowed block Types
	 */
	function allowed_block_types( $allowed_block_types, \WP_Post $post ) { // phpcs:ignore
		$blocks = [
			'core/button',
			'core/code',
			'core/block',
			'core/group',
			'core/image',
			'core/heading',
			'core/separator',
			'core-embed/youtube',
			'core-embed/vimeo',
			'core/list',
			'core/file',
			'core/missing',
			'core/paragraph',
			'core/template',
			'core/quote',
		];

		return $blocks;
	}

	/**
	 * Register Theme Functionality
	 */
	public function register_theme_functionality() {
		add_theme_support( 'custom-background', [
			'default-color' => 'f1f1f1',
		] );
	}
}
