<?php

namespace Duplicator\Core\Notifications;

class Notice
{
    /**
     * Not dismissible.
     *
     * Constant attended to use as the value of the $args['dismiss'] argument.
     * DISMISS_NONE means that the notice is not dismissible.
     */
    const DISMISS_NONE = 0;

    /**
     * Dismissible global.
     *
     * Constant attended to use as the value of the $args['dismiss'] argument.
     * DISMISS_GLOBAL means that the notice will have the dismiss button, and after clicking this button, the notice will be dismissed for all users.
     */
    const DISMISS_GLOBAL = 1;

    /**
     * Dismissible per user.
     *
     * Constant attended to use as the value of the $args['dismiss'] argument.
     * DISMISS_USER means that the notice will have the dismiss button, and after clicking this button, the notice will be dismissed only for the current user..
     */
    const DISMISS_USER = 2;

    /**
     * Constant for notice type info with gray left border
     */
    const NOTICE_TYPE_INFO = 'info';

    /**
     * Constant for notice type warning with yellow left border
     */
    const NOTICE_TYPE_WARNING = 'warning';

    /**
     * Constant for notice type warning with red left border
     */
    const NOTICE_TYPE_ERROR = 'error';

    /**
     * Constant for notice type warning with green left border
     */
    const NOTICE_TYPE_SUCCESS = 'success';

    /**
     * Constant for notice id default prefix
     */
    const DEFAULT_PREFIX = 'dup-notice-';

    /**
     * Constant for addition notice id prefix in case it's a global notice
     */
    const GLOBAL_PREFIX = 'global-';

    /**
     * The wp-options key in which the notices are stored
     */
    const DISMISSED_NOTICES_OPTKEY = 'duplicator_dismissed_admin_notices';

    /**
     * Notices.
     *
     * @var array
     */
    private static $notices = array();

    /**
     * Init.
     *
     * @return void
     */
    public static function init()
    {
        static::hooks();
    }

    /**
     * Hooks.
     *
     * @return void
     */
    public static function hooks()
    {
        add_action('admin_notices', array(__CLASS__, 'display'), PHP_INT_MAX);
        add_action('wp_ajax_dup_notice_dismiss', array(__CLASS__, 'dismissAjax'));
    }

    /**
     * Enqueue assets.
     *
     * @return void
     */
    private static function enqueues()
    {
        wp_enqueue_script(
            'dup-admin-notices',
            DUPLICATOR_PLUGIN_URL . "assets/js/notifications/notices.js",
            array('jquery'),
            DUPLICATOR_VERSION,
            true
        );

        wp_localize_script(
            'dup-admin-notices',
            'dup_admin_notices',
            array(
                'ajax_url' => admin_url('admin-ajax.php'),
                'nonce'    => wp_create_nonce('duplicator-admin-notice'),
            )
        );
    }

    /**
     * Display the notices.
     *
     * @return void
     */
    public static function display()
    {

        $dismissed_notices = get_user_meta(get_current_user_id(), self::DISMISSED_NOTICES_OPTKEY, true);
        $dismissed_notices = is_array($dismissed_notices) ? $dismissed_notices : array();
        $dismissed_notices = array_merge($dismissed_notices, (array)get_option(self::DISMISSED_NOTICES_OPTKEY, array()));

        foreach (self::$notices as $slug => $notice) {
            if (isset($dismissed_notices[$slug])) {
                unset(self::$notices[$slug]);
            }
        }

        $output = implode('', self::$notices);

        echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

        // Enqueue script only when it's needed.
        if (strpos($output, 'is-dismissible') !== false) {
            self::enqueues();
        }
    }

    /**
     * Add notice to the registry.
     *
     * @param string $message Message to display.
     * @param string $slug    Unique slug identifying the notice
     * @param string $type    Type of the notice. Can be [ '' (default) | 'info' | 'error' | 'success' | 'warning' ].
     * @param array  $args    The array of additional arguments. Please see the $defaults array below.
     *
     * @return void
     */
    public static function add($message, $slug, $type = '', $args = array())
    {
        $defaults = array(
            'dismiss' => self::DISMISS_NONE, // Dismissible level: one of the self::DISMISS_* const. By default notice is not dismissible.
            'autop'   => true,               // `false` if not needed to pass message through wpautop().
            'class'   => ''                 // Additional CSS class.
        );

        $args    = wp_parse_args($args, $defaults);
        $dismiss = (int)$args['dismiss'];
        $classes = array();

        if (!empty($type)) {
            $classes[] = 'notice-' . esc_attr(sanitize_key($type));
        }

        if (!empty($args['class'])) {
            $classes[] = esc_attr(sanitize_key($args['class']));
        }

        if ($dismiss > self::DISMISS_NONE) {
            $classes[] = 'is-dismissible';
        }

        $id      = $dismiss === self::DISMISS_GLOBAL ? self::DEFAULT_PREFIX . self::GLOBAL_PREFIX . $slug : self::DEFAULT_PREFIX . $slug;
        $message = $args['autop'] ? wpautop($message) : $message;
        $notice  = sprintf(
            '<div class="notice dup-notice %s" id="%s">%s</div>',
            esc_attr(implode(' ', $classes)),
            esc_attr($id),
            $message
        );

        self::$notices[$slug] = $notice;
    }

    /**
     * Add multistep notice.
     *
     * @param array  $steps Array of info for each step.
     * @param string $slug  Unique slug identifying the notice
     * @param string $type  Type of the notice. Can be [ '' (default) | 'info' | 'error' | 'success' | 'warning' ].
     * @param array  $args  Array of additional arguments. Details in the self::add() method.
     *
     * @return void
     */
    public static function addMultistep($steps, $slug, $type = '', $args = array())
    {
        $content = '<div class="dup-multi-notice">';
        foreach ($steps as $i => $step) {
            $hide     = $i === 0 ? '' : ' style="display: none;"';
            $content .= '<div class="dup-multi-notice-step dup-multi-notice-step-' . $i . '"' . $hide . '>';

            $content .= $step['message'];
            $content .= "<p>";
            foreach ($step["links"] as $link) {
                $url     = isset($link['url']) ? $link['url'] : "#";
                $target  = isset($link['url']) ? 'target="_blank"' : '';
                $switch  = isset($link['switch']) ? ' data-step="' . $link['switch'] . '"' : '';
                $dismiss = isset($link['dismiss']) && $link['dismiss'] ? ' class="dup-notice-dismiss"' : '';

                $content .= '<a href="' . $url . '"' . $dismiss . $switch . $target . '>' . $link['text'] . '</a><br>';
            }
            $content .= "</p>";
            $content .= "</div>";
        }
        $content .= "</div>";

        self::add($content, $slug, $type, $args);
    }

    /**
     * Add info notice.
     *
     * @param string $message Message to display.
     * @param string $slug    Unique slug identifying the notice
     * @param array  $args    Array of additional arguments. Details in the self::add() method.
     *
     * @return void
     */
    public static function info($message, $slug, $args = array())
    {

        self::add($message, $slug, self::NOTICE_TYPE_INFO, $args);
    }

    /**
     * Add error notice.
     *
     * @param string $message Message to display.
     * @param string $slug    Unique slug identifying the notice
     * @param array  $args    Array of additional arguments. Details in the self::add() method.
     *
     * @return void
     */
    public static function error($message, $slug, $args = array())
    {

        self::add($message, $slug, self::NOTICE_TYPE_ERROR, $args);
    }

    /**
     * Add success notice.
     *
     * @param string $message Message to display.
     * @param string $slug    Unique slug identifying the notice
     * @param array  $args    Array of additional arguments. Details in the self::add() method.
     *
     * @return void
     */
    public static function success($message, $slug, $args = array())
    {

        self::add($message, $slug, self::NOTICE_TYPE_SUCCESS, $args);
    }

    /**
     * Add warning notice.
     *
     * @param string $message Message to display.
     * @param string $slug    Unique slug identifying the notice
     * @param array  $args    Array of additional arguments. Details in the self::add() method.
     *
     * @return void
     */
    public static function warning($message, $slug, $args = array())
    {

        self::add($message, $slug, self::NOTICE_TYPE_WARNING, $args);
    }

    /**
     * AJAX routine that updates dismissed notices meta data.
     *
     * @return void
     */
    public static function dismissAjax()
    {

        // Run a security check.
        check_ajax_referer('duplicator-admin-notice', 'nonce');

        // Sanitize POST data.
        $post = array_map('sanitize_key', wp_unslash($_POST));

        // Update notices meta data.
        if (strpos($post['id'], self::GLOBAL_PREFIX) !== false) {
            // Check for permissions.
            if (!current_user_can('manage_options')) {
                wp_send_json_error();
            }

            $notices = self::dismissGlobal($post['id']);
            $level   = self::DISMISS_GLOBAL;
        } else {
            $notices = self::dismissUser($post['id']);
            $level   = self::DISMISS_USER;
        }

        /**
         * Allows developers to apply additional logic to the dismissing notice process.
         * Executes after updating option or user meta (according to the notice level).
         *
         * @param string  $notice_id Notice ID (slug).
         * @param integer $level     Notice level.
         * @param array   $notices   Dismissed notices.
         */
        do_action('duplicator_admin_notice_dismiss_ajax', $post['id'], $level, $notices);

        wp_send_json_success(
            array(
                'id'      => $post['id'],
                'time'    => time(),
                'level'   => $level,
                'notices' => $notices
            )
        );
    }

    /**
     * AJAX sub-routine that updates dismissed notices option.
     *
     * @param string $id Notice Id.
     *
     * @return array Notices.
     */
    private static function dismissGlobal($id)
    {

        $id           = str_replace(self::GLOBAL_PREFIX, '', $id);
        $notices      = get_option(self::DISMISSED_NOTICES_OPTKEY, array());
        $notices[$id] = array(
            'time' => time()
        );

        update_option(self::DISMISSED_NOTICES_OPTKEY, $notices, true);

        return $notices;
    }

    /**
     *  AJAX sub-routine that updates dismissed notices user meta.
     *
     * @param string $id Notice Id.
     *
     * @return array Notices.
     */
    private static function dismissUser($id)
    {

        $user_id      = get_current_user_id();
        $notices      = get_user_meta($user_id, self::DISMISSED_NOTICES_OPTKEY, true);
        $notices      = !is_array($notices) ? array() : $notices;
        $notices[$id] = array(
            'time' => time()
        );

        update_user_meta($user_id, self::DISMISSED_NOTICES_OPTKEY, $notices);

        return $notices;
    }

    /**
     * Delete related option
     *
     * @return void
     */
    public static function deleteOption()
    {
        delete_option(self::DISMISSED_NOTICES_OPTKEY);
    }
}
