<?php
/**
 * Admin screen for managing Rule Categories.
 *
 * @link       https://personalizewp.com
 * @since      1.0.0
 *
 * @package    PersonalizeWP
 * @subpackage PersonalizeWP/Admin
 */

namespace PersonalizeWP\Admin;

use PersonalizeWP\Admin\Admin;
use PersonalizeWP\Admin\Admin_Base_Page;
use PersonalizeWP\Model\Category;

defined( 'ABSPATH' ) || exit; // Exit if accessed directly.

/**
 * Class Categories Admin
 */
class PageCategories extends Admin_Base_Page {

	/**
	 * Slug for Categories admin page
	 *
	 * @since 1.2.0
	 */
	const SLUG = 'personalizewp/categories';


	/**
	 * URL for main Rule Categories admin page (listing)
	 *
	 * @since 3.2.0
	 *
	 * @var string
	 */
	public static $index_url = 'admin.php?page=' . self::SLUG;

	/**
	 * URL for admin page, creating a category
	 *
	 * @since 3.2.0
	 *
	 * @var string
	 */
	public static $create_url = 'admin.php?page=' . self::SLUG . '&' . Admin::URL_ACTION . '=create';

	/**
	 * URL for admin page, editing a category.
	 * Requires category ID to be appended.
	 *
	 * @since 3.2.0
	 *
	 * @var string
	 */
	public static $edit_url = 'admin.php?page=' . self::SLUG . '&' . Admin::URL_ACTION . '=edit&id=';

	/**
	 * URL for admin page, deleting a category.
	 * Requires category ID to be appended.
	 *
	 * @since 3.2.0
	 *
	 * @var string
	 */
	public static $delete_url = 'admin.php?page=' . self::SLUG . '&' . Admin::URL_ACTION . '=delete&id=';

	/**
	 * URL for admin page, duplicating a rule.
	 * Requires category ID to be appended.
	 *
	 * @since 3.2.0
	 *
	 * @var string
	 */
	public static $duplicate_url = 'admin.php?page=' . self::SLUG . '&' . Admin::URL_ACTION . '=show&id=';

	/**
	 * URL for admin page, filtering Rules by category.
	 * Requires category ID to be appended.
	 *
	 * @since 3.2.0
	 *
	 * @var string
	 */
	public static $rules_url = 'admin.php?page=' . self::SLUG . '&' . Admin::URL_ACTION . '=rules&id=';

	/**
	 * Constructor
	 */
	public function __construct() {
		parent::__construct();
		$this->page_title = __( 'Personalization', 'personalizewp' );

		add_action( 'admin_init', [ $this, 'process' ] );
		add_filter( 'set-screen-option', [ $this, 'set_screen_options' ], 10, 3 );
		add_filter( 'parent_file', [ $this, 'switch_active_menu_item' ] );
	}

	/**
	 * Display the Dashboard screen options.
	 *
	 * @since 1.2.0
	 */
	public function dashboard_screen_options() {
		$screen = get_current_screen();

		$is_screen = is_object( $screen ) && str_ends_with( $screen->id, self::SLUG );
		if ( ! $is_screen ) {
			return;
		}
		// Re-trigger core WP, as admins' call runs way too early IMHO.
		set_screen_options();

		$args = array(
			'label'   => __( 'Number of Categories per page:', 'personalizewp' ),
			'default' => 20,
			'option'  => 'edit_pwp_categories_per_page',
		);
		add_screen_option( 'per_page', $args );
	}

	/**
	 * Store the screen options.
	 *
	 * @since 1.2.0
	 *
	 * @param mixed  $screen_option The value to save instead of the option value.
	 *                              Default false (to skip saving the current option).
	 * @param string $option        The option name.
	 * @param int    $value         The option value.
	 *
	 * @return string
	 */
	public function set_screen_options( $screen_option, $option, $value ) {
		if ( 'edit_pwp_categories_per_page' === $option ) {
			return $value;
		}
		return $screen_option;
	}

	/**
	 * Modifies admin menu of PersonalizeWP
	 * Sets active page for sub-sections which are invisible in the menu.
	 *
	 * @since 3.3.0
	 *
	 * @param string $parent_file The parent file.
	 *
	 * @return string
	 */
	public function switch_active_menu_item( $parent_file ) {
		global $plugin_page;

		if ( self::SLUG === $plugin_page ) {
			// Pretend to be on Rules screen, for highlighted menu item.
			$plugin_page = PageRules::SLUG;
		}

		return $parent_file;
	}

	/**
	 * Process - typically used for processing POST data
	 *
	 * @return void
	 */
	public function process() {
		// Double check for user caps before any possible processing of data.
		if ( ! current_user_can( Admin::CAP ) ) {
			return;
		}

		if ( self::SLUG !== $this->current_page ) {
			return;
		}

		$this->form_post = filter_input( INPUT_POST, 'personalizewp_form', \FILTER_CALLBACK, [ 'options' => 'sanitize_text_field' ] );

		if ( ! empty( $this->form_post ) ) {
			switch ( $this->current_action ) {
				case 'create':
					check_admin_referer( 'category-create' ); // Immediately dies if failing.
					$this->store_action( $this->form_post );
					break;

				case 'edit':
					if ( ! empty( $this->form_post['id'] ) ) {
						$cat_id = (int) $this->form_post['id'];
						check_admin_referer( 'category-update-' . $cat_id );
						$this->update_action( $cat_id, $this->form_post );
					}
					break;
			}
		}

		switch ( $this->current_action ) {
			case 'delete':
				if ( ! empty( $_GET['id'] ) ) {
					$cat_id = (int) sanitize_text_field( wp_unslash( $_GET['id'] ) );
					check_admin_referer( 'category-delete-' . $cat_id );
					$this->delete_action( $cat_id );
				}
				break;
		}
	}

	/**
	 * Route to the correct action within the page
	 *
	 * @return void
	 */
	public function route() {
		// Double check for user caps before any possible processing of data.
		if ( ! current_user_can( Admin::CAP ) ) {
			return;
		}

		if ( self::SLUG !== $this->current_page ) {
			return;
		}

		switch ( $this->current_action ) {
			case 'create':
				if ( ! empty( $_REQUEST['_wpnonce'] ) ) {
					// Processing create has failed, so re-load the form.
					check_admin_referer( 'category-create' );
					$this->create_screen( $this->form_post );
				} else {
					$this->create_screen();
				}
				return;

			case 'edit':
				if ( ! empty( $_GET['id'] ) ) {
					$cat_id = (int) sanitize_text_field( wp_unslash( $_GET['id'] ) );
					$this->edit_screen( $cat_id );
					return;
				}
				break;

			case 'rules':
				if ( ! empty( $_GET['id'] ) ) {
					$cat_id = (int) sanitize_text_field( wp_unslash( $_GET['id'] ) );
					$this->view_rules_screen( $cat_id );
					return;
				}
				break;
		}

		$this->index_screen();
	}

	/**
	 * Display onboarding message, only if not dismissed.
	 *
	 * @since 3.2.0
	 *
	 * @return void
	 */
	protected function maybe_display_onboarding() {
		$is_onboarding = ( '1' !== get_option( 'pwp_admin_onboarding_dismissed', '' ) );
		// Dismissing this updates the option globally, dismissing all the 'first time' messaging.
		if ( ! $is_onboarding ) {
			return;
		}
		// The option key ensures persistancy.
		wp_admin_notice(
			sprintf(
				'<span class="dashicons dashicons-info"></span><p><strong>%1$s</strong></p><p>%2$s</p>',
				esc_html__( 'First time configuration', 'personalizewp' ),
				esc_html__( 'PersonalizeWP provides a number of pre-built rules and categories that are ready to use on your site, which you can see below. If you want to add your own, just click on the Create Rule or Create Category button.', 'personalizewp' ),
			),
			array(
				'type'               => 'info',
				'dismissible'        => true,
				'paragraph_wrap'     => false,
				'additional_classes' => array( 'onboarding' ),
				'attributes'         => array( 'data-dismiss-type' => 'onboarding' ),
			)
		);
	}

	/**
	 * Index Screen
	 */
	public function index_screen() {

		$this->display_header();

		$status_message = '';
		// Trigger messaging from actions.
		if ( isset( $_REQUEST['created'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- this is only displaying a message with no dynamic values.
			$status_message = __( 'Category created', 'personalizewp' );
		}
		if ( isset( $_REQUEST['updated'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- this is only displaying a message with no dynamic values.
			$status_message = __( 'Category updated', 'personalizewp' );
		}
		if ( isset( $_REQUEST['deleted'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- this is only displaying a message with no dynamic values.
			$status_message = __( 'Category deleted', 'personalizewp' );
		}
		if ( $status_message ) {
			wp_admin_notice(
				'<span class="dashicons dashicons-yes-alt"></span>' . $status_message,
				array(
					'type'        => 'success',
					'id'          => 'message',
					'dismissible' => true,
				)
			);
		}

		$this->maybe_display_onboarding();

		require_once plugin_dir_path( __DIR__ ) . 'admin/partials/categories/index.php';
		$this->display_footer();
	}

	/**
	 * Create Screen - Create a placeholder category object display form
	 *
	 * @param array $data Form data
	 */
	protected function create_screen( $data = [] ) {

		$this->display_header();

		$category = new Category();

		if ( $data ) {
			$category->populateFromArray( $data );
		}

		require_once plugin_dir_path( __DIR__ ) . 'admin/partials/categories/create.php';
		$this->display_footer();
	}

	/**
	 * Store action - process data from form submitted on "create" page
	 * Redirects after validating and storing.
	 *
	 * @param array $data Form data
	 */
	protected function store_action( $data ) {
		$this->validate( $data );

		if ( empty( $this->getError() ) ) {
			$this->saveCategory( $data );

			wp_safe_redirect( add_query_arg( array( 'created' => 1 ), admin_url( self::$index_url ) ) );
			exit;
		}
	}

	/**
	 * Edit Screen - display form for existing category
	 *
	 * @param integer $id Category ID
	 */
	protected function edit_screen( $id ) {
		$category = Category::find( $id );

		if ( ! $category ) {
			$this->showError( __( 'You attempted to edit an item that does not exist. Perhaps it was deleted?', 'personalizewp' ) );
		}

		$this->display_header();
		require_once plugin_dir_path( __DIR__ ) . 'admin/partials/categories/edit.php';
		$this->display_footer();
	}

	/**
	 * Rules Screen - display rules filtered by category
	 *
	 * @param integer $id Category ID
	 */
	protected function view_rules_screen( $id ) {
		// This is used within the partial template below.
		$category = Category::find( $id );

		if ( ! $category ) {
			$this->showError( __( 'You attempted to edit an item that does not exist. Perhaps it was deleted?', 'personalizewp' ) );
		}

		$this->display_header();
		require_once plugin_dir_path( __DIR__ ) . 'admin/partials/categories/rules.php';
		$this->display_footer();
	}

	/**
	 * Update action - process data from form submitted on "edit" page
	 * Redirects after validating and storing.
	 *
	 * @param integer $id   Category ID
	 * @param array   $data Form data
	 */
	protected function update_action( $id, $data ) {
		$category = Category::find( $id );

		if ( ! $category ) {
			$this->showError( __( 'You attempted to edit an item that does not exist. Perhaps it was deleted?', 'personalizewp' ) );
		}

		if ( ! $category->can_edit ) {
			$this->showError( __( 'This category cannot be edited', 'personalizewp' ) );
		}

		$this->validate( $data, true );

		if ( empty( $this->getError() ) ) {
			$this->saveCategory( $data, $id );

			wp_safe_redirect( add_query_arg( array( 'updated' => 1 ), admin_url( self::$index_url ) ) );
			exit;
		}
	}

	/**
	 * Delete action
	 *
	 * @param integer $id Category ID
	 */
	protected function delete_action( $id ) {
		$category = Category::find( $id );

		if ( ! $category ) {
			// Nothing to do
			wp_safe_redirect( admin_url( self::$index_url ) );
			exit;
		}

		if ( ! $category->can_delete ) {
			$this->showError( __( 'Category cannot be deleted', 'personalizewp' ) );
		}

		Category::delete( $id );

		wp_safe_redirect( add_query_arg( array( 'deleted' => 1 ), admin_url( self::$index_url ) ) );
		exit;
	}

	/**
	 * Save category to DB (either create or update)
	 *
	 * @param  array   $data Form data
	 * @param  integer $id   Category ID
	 * @return Category
	 */
	protected function saveCategory( $data, $id = null ) {
		if ( $id ) {
			$category = Category::find( $id );
		}

		if ( empty( $category ) ) {
			$category = new Category();
		}

		$category->populateFromArray(
			[
				'name' => $data['name'],
			]
		);

		$category->save();

		return $category;
	}

	/**
	 * Validate data, likely from submitted form
	 *
	 * @param  array   $data           Form data
	 * @param  boolean $existing_model True if we're editing an existing model
	 * @return void
	 */
	protected function validate( $data, $existing_model = false ) {
		if ( empty( $data ) ) {
			$this->addValidationError( 'form', __( 'Form is not valid', 'personalizewp' ) );
			return;
		}

		if ( $existing_model ) {
			if ( empty( $data['id'] ) ) {
				$this->addValidationError( 'form', __( 'ID is required to update a category', 'personalizewp' ) );
			}
		}

		if ( empty( $data['name'] ) ) {
			$this->addValidationError( 'name', __( 'Name is required', 'personalizewp' ) );
		}

		if ( Category::check_name( $data['name'], $data['id'] ) ) {
			$this->addValidationError( 'name', __( 'This category name already exists. Please choose a different name.', 'personalizewp' ) );
		}
	}
}
