<?php

/**
 * @package     Joomla.Plugin
 * @subpackage  System.guidedtours
 *
 * @copyright   (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\Plugin\System\GuidedTours\Extension;

use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Date\Date;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Object\CMSObject;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Session\Session;
use Joomla\Component\Guidedtours\Administrator\Extension\GuidedtoursComponent;
use Joomla\Component\Guidedtours\Administrator\Model\TourModel;
use Joomla\Database\DatabaseAwareTrait;
use Joomla\Database\ParameterType;
use Joomla\Event\DispatcherInterface;
use Joomla\Event\Event;
use Joomla\Event\SubscriberInterface;

// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects

/**
 * Guided Tours plugin to add interactive tours to the administrator interface.
 *
 * @since  4.3.0
 */
final class GuidedTours extends CMSPlugin implements SubscriberInterface
{
    use DatabaseAwareTrait;

    /**
     * A mapping for the step types
     *
     * @var    string[]
     * @since  4.3.0
     */
    protected $stepType = [
        GuidedtoursComponent::STEP_NEXT        => 'next',
        GuidedtoursComponent::STEP_REDIRECT    => 'redirect',
        GuidedtoursComponent::STEP_INTERACTIVE => 'interactive',
    ];

    /**
     * A mapping for the step interactive types
     *
     * @var    string[]
     * @since  4.3.0
     */
    protected $stepInteractiveType = [
        GuidedtoursComponent::STEP_INTERACTIVETYPE_FORM_SUBMIT    => 'submit',
        GuidedtoursComponent::STEP_INTERACTIVETYPE_TEXT           => 'text',
        GuidedtoursComponent::STEP_INTERACTIVETYPE_OTHER          => 'other',
        GuidedtoursComponent::STEP_INTERACTIVETYPE_BUTTON         => 'button',
        GuidedtoursComponent::STEP_INTERACTIVETYPE_CHECKBOX_RADIO => 'checkbox_radio',
        GuidedtoursComponent::STEP_INTERACTIVETYPE_SELECT         => 'select',
    ];

    /**
     * An internal flag whether plugin should listen any event.
     *
     * @var bool
     *
     * @since   4.3.0
     */
    protected static $enabled = false;

    /**
     * Constructor
     *
     * @param   DispatcherInterface  $dispatcher  The object to observe
     * @param   array                $config      An optional associative array of configuration settings.
     * @param   boolean              $enabled     An internal flag whether plugin should listen any event.
     *
     * @since   4.3.0
     */
    public function __construct(DispatcherInterface $dispatcher, array $config = [], bool $enabled = false)
    {
        self::$enabled = $enabled;

        parent::__construct($dispatcher, $config);
    }

    /**
     * function for getSubscribedEvents : new Joomla 4 feature
     *
     * @return array
     *
     * @since   4.3.0
     */
    public static function getSubscribedEvents(): array
    {
        return self::$enabled ? [
            'onAjaxGuidedtours'   => 'startTour',
            'onBeforeCompileHead' => 'onBeforeCompileHead',
        ] : [];
    }

    /**
     * Retrieve and starts a tour and its steps through Ajax.
     *
     * @return null|object
     *
     * @since   4.3.0
     */
    public function startTour(Event $event)
    {
        $tourId  = (int) $this->getApplication()->getInput()->getInt('id');
        $tourUid = $this->getApplication()->getInput()->getString('uid', '');
        $tourUid = $tourUid !== '' ? urldecode($tourUid) : '';

        $tour = null;

        // Load plugin language files
        $this->loadLanguage();

        if ($tourId > 0) {
            $tour = $this->getTour($tourId);
        } elseif ($tourUid !== '') {
            $tour = $this->getTour($tourUid);
        }

        $event->setArgument('result', $tour ?? new \stdClass());

        return $tour;
    }

    /**
     * Listener for the `onBeforeCompileHead` event
     *
     * @return  void
     *
     * @since   4.3.0
     */
    public function onBeforeCompileHead()
    {
        $app  = $this->getApplication();
        $doc  = $app->getDocument();
        $user = $app->getIdentity();

        if ($user != null && $user->id > 0) {
            // Load plugin language files.
            $this->loadLanguage();

            Text::script('JCANCEL');
            Text::script('PLG_SYSTEM_GUIDEDTOURS_BACK');
            Text::script('PLG_SYSTEM_GUIDEDTOURS_COMPLETE');
            Text::script('PLG_SYSTEM_GUIDEDTOURS_COULD_NOT_LOAD_THE_TOUR');
            Text::script('PLG_SYSTEM_GUIDEDTOURS_HIDE_FOREVER');
            Text::script('PLG_SYSTEM_GUIDEDTOURS_NEXT');
            Text::script('PLG_SYSTEM_GUIDEDTOURS_STEP_NUMBER_OF');
            Text::script('PLG_SYSTEM_GUIDEDTOURS_TOUR_ERROR');
            Text::script('PLG_SYSTEM_GUIDEDTOURS_TOUR_ERROR_RESPONSE');
            Text::script('PLG_SYSTEM_GUIDEDTOURS_TOUR_INVALID_RESPONSE');

            $doc->addScriptOptions('com_guidedtours.token', Session::getFormToken());
            $doc->addScriptOptions('com_guidedtours.autotour', '');

            // Load required assets.
            $doc->getWebAssetManager()
                ->usePreset('plg_system_guidedtours.guidedtours');

            $params = ComponentHelper::getParams('com_guidedtours');

            // Check if the user has opted out of auto-start
            $userAuthorizedAutostart = $user->getParam('allowTourAutoStart', $params->get('allowTourAutoStart', 1));
            if (!$userAuthorizedAutostart) {
                return;
            }

            // The following code only relates to the auto-start functionality.
            // First, we get the tours for the context.

            $factory = $app->bootComponent('com_guidedtours')->getMVCFactory();

            $toursModel = $factory->createModel(
                'Tours',
                'Administrator',
                ['ignore_request' => true]
            );

            $toursModel->setState('filter.extension', $app->getInput()->getCmd('option', 'com_cpanel'));
            $toursModel->setState('filter.published', 1);
            $toursModel->setState('filter.access', $user->getAuthorisedViewLevels());

            if (Multilanguage::isEnabled()) {
                $toursModel->setState('filter.language', ['*', $app->getLanguage()->getTag()]);
            }

            $tours = $toursModel->getItems();
            foreach ($tours as $tour) {
                // Look for the first autostart tour, if any.
                if ($tour->autostart) {
                    $db         = $this->getDatabase();
                    $profileKey = 'guidedtour.id.' . $tour->id;

                    // Check if the tour state has already been saved some time before.
                    $query = $db->getQuery(true)
                        ->select($db->quoteName('profile_value'))
                        ->from($db->quoteName('#__user_profiles'))
                        ->where($db->quoteName('user_id') . ' = :user_id')
                        ->where($db->quoteName('profile_key') . ' = :profileKey')
                        ->bind(':user_id', $user->id, ParameterType::INTEGER)
                        ->bind(':profileKey', $profileKey, ParameterType::STRING);

                    try {
                        $result = $db->setQuery($query)->loadResult();
                    } catch (\Exception $e) {
                        // Do not start the tour.
                        continue;
                    }

                    // A result has been found in the user profiles table
                    if (!\is_null($result)) {
                        $values = json_decode($result, true);

                        if (empty($values)) {
                            // Do not start the tour.
                            continue;
                        }

                        if ($values['state'] === 'skipped' || $values['state'] === 'completed') {
                            // Do not start the tour.
                            continue;
                        }

                        if ($values['state'] === 'delayed') {
                            $delay       = $params->get('delayed_time', '60');
                            $currentTime = Date::getInstance();
                            $loggedTime  = new Date($values['time']['date']);

                            if ($loggedTime->add(new \DateInterval('PT' . $delay . 'M')) > $currentTime) {
                                // Do not start the tour.
                                continue;
                            }
                        }
                    }

                    // We have a tour to auto start. No need to go any further.
                    $doc->addScriptOptions('com_guidedtours.autotour', $tour->id);
                    break;
                }
            }
        }
    }

    /**
     * Get a tour and its steps or null if not found
     *
     * @param   integer|string  $tourId  The ID or Uid of the tour to load
     *
     * @return null|object
     *
     * @since   4.3.0
     */
    private function getTour($tourId)
    {
        $app = $this->getApplication();

        $factory = $app->bootComponent('com_guidedtours')->getMVCFactory();

        /** @var TourModel $tourModel */
        $tourModel = $factory->createModel(
            'Tour',
            'Administrator',
            ['ignore_request' => true]
        );

        $item = $tourModel->getItem($tourId);

        return $this->processTour($item);
    }

    /**
     * Return a tour and its steps or null if not found
     *
     * @param   CMSObject  $item  The tour to load
     *
     * @return null|object
     *
     * @since   5.0.0
     */
    private function processTour($item)
    {
        $app = $this->getApplication();

        $user    = $app->getIdentity();
        $factory = $app->bootComponent('com_guidedtours')->getMVCFactory();

        if (empty($item->id) || $item->published < 1 || !\in_array($item->access, $user->getAuthorisedViewLevels())) {
            return null;
        }

        // We don't want to show all parameters, so take only a subset of the tour attributes
        $tour = new \stdClass();

        $tour->id        = $item->id;
        $tour->autostart = $item->autostart;

        $stepsModel = $factory->createModel(
            'Steps',
            'Administrator',
            ['ignore_request' => true]
        );

        $stepsModel->setState('filter.tour_id', $item->id);
        $stepsModel->setState('filter.published', 1);
        $stepsModel->setState('list.ordering', 'a.ordering');
        $stepsModel->setState('list.direction', 'ASC');

        $steps = $stepsModel->getItems();

        $tour->steps = [];

        $temp = new \stdClass();

        $temp->id          = 0;
        $temp->title       = $this->getApplication()->getLanguage()->_($item->title);
        $temp->description = $this->getApplication()->getLanguage()->_($item->description);
        $temp->description = $this->fixImagePaths($temp->description);
        $temp->url         = $item->url;

        // Set the start label for the tour.
        $temp->start_label = Text::_('PLG_SYSTEM_GUIDEDTOURS_START');
        // What's new tours have a different label.
        if (str_contains($item->uid, 'joomla-whatsnew')) {
            $temp->start_label = Text::_('PLG_SYSTEM_GUIDEDTOURS_NEXT');
        }

        $tour->steps[] = $temp;

        foreach ($steps as $i => $step) {
            $temp = new \stdClass();

            $temp->id               = $i + 1;
            $temp->title            = $this->getApplication()->getLanguage()->_($step->title);
            $temp->description      = $this->getApplication()->getLanguage()->_($step->description);
            $temp->description      = $this->fixImagePaths($temp->description);
            $temp->position         = $step->position;
            $temp->target           = $step->target;
            $temp->type             = $this->stepType[$step->type];
            $temp->interactive_type = $this->stepInteractiveType[$step->interactive_type];
            $temp->params           = $step->params;
            $temp->url              = $step->url;
            $temp->tour_id          = $step->tour_id;
            $temp->step_id          = $step->id;

            $tour->steps[] = $temp;
        }

        return $tour;
    }

    /**
     * Return a modified version of a given string with usable image paths for tours
     *
     * @param   string  $description  The string to fix
     *
     * @return  string
     *
     * @since  5.2.0
     */
    private function fixImagePaths($description)
    {
        return preg_replace(
            [
                '*src="(?!administrator\/)images/*',
                '*src="media/*',
            ],
            [
                'src="../images/',
                'src="../media/',
            ],
            $description
        );
    }
}
