<?php

/**
 * @package       Joomla.Administrator
 * @subpackage    com_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\Component\Guidedtours\Administrator\Model;

use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Log\Log;
use Joomla\CMS\MVC\Model\AdminModel;
use Joomla\CMS\Object\CMSObject;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Database\ParameterType;
use Joomla\Utilities\ArrayHelper;

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

/**
 * Model class for tour
 *
 * @since  4.3.0
 */
class TourModel extends AdminModel
{
    /**
     * The prefix to use with controller messages.
     *
     * @var   string
     * @since 4.3.0
     */
    protected $text_prefix = 'COM_GUIDEDTOURS';

    /**
     * Type alias for content type
     *
     * @var string
     * @since 4.3.0
     */
    public $typeAlias = 'com_guidedtours.tour';

    /**
     * Method to save the form data.
     *
     * @param   array  $data  The form data.
     *
     * @return  boolean True on success.
     *
     * @since  4.3.0
     */
    public function save($data)
    {
        $input = Factory::getApplication()->getInput();

        // Language keys must include GUIDEDTOUR to prevent save issues
        if (strpos($data['description'], 'GUIDEDTOUR') !== false) {
            $data['description'] = strip_tags($data['description']);
        }

        if ($input->get('task') == 'save2copy') {
            $origTable = clone $this->getTable();
            $origTable->load($input->getInt('id'));

            $data['published'] = 0;
        }

        // Set step language to parent tour language on save.
        $id   = $data['id'];
        $lang = $data['language'];

        $this->setStepsLanguage($id, $lang);

        return parent::save($data);
    }

    /**
     * Prepare and sanitise the table prior to saving.
     *
     * @param   \Joomla\CMS\Table\Table  $table  The Table object
     *
     * @return  void
     *
     * @since  4.3.0
     */
    protected function prepareTable($table)
    {
        $date = Factory::getDate()->toSql();

        $table->title = htmlspecialchars_decode($table->title, ENT_QUOTES);

        if (empty($table->id)) {
            // Set the values
            $table->created = $date;

            // Set ordering to the last item if not set
            if (empty($table->ordering)) {
                $db    = $this->getDatabase();
                $query = $db->getQuery(true)
                    ->select('MAX(ordering)')
                    ->from($db->quoteName('#__guidedtours'));
                $db->setQuery($query);
                $max = $db->loadResult();

                $table->ordering = $max + 1;
            }
        } else {
            // Set the values
            $table->modified    = $date;
            $table->modified_by = $this->getCurrentUser()->id;
        }
    }

    /**
     * Abstract method for getting the form from the model.
     *
     * @param   array    $data      Data for the form.
     * @param   boolean  $loadData  True if the form is to load its own data (default case), false if not.
     *
     * @return \JForm|boolean  A JForm object on success, false on failure
     *
     * @since  4.3.0
     */
    public function getForm($data = [], $loadData = true)
    {
        // Get the form.
        $form = $this->loadForm(
            'com_guidedtours.tour',
            'tour',
            [
                'control'   => 'jform',
                'load_data' => $loadData,
            ]
        );

        if (empty($form)) {
            return false;
        }

        $id = $data['id'] ?? $form->getValue('id');

        $item = $this->getItem($id);

        // Modify the form based on access controls.
        if (!$this->canEditState((object) $item)) {
            $form->setFieldAttribute('published', 'disabled', 'true');
            $form->setFieldAttribute('published', 'required', 'false');
            $form->setFieldAttribute('published', 'filter', 'unset');
        }

        $currentDate = Factory::getDate()->toSql();

        $form->setFieldAttribute('created', 'default', $currentDate);
        $form->setFieldAttribute('modified', 'default', $currentDate);

        return $form;
    }

    /**
     * Method to get the data that should be injected in the form.
     *
     * @return mixed  The data for the form.
     *
     * @since  4.3.0
     */
    protected function loadFormData()
    {
        // Check the session for previously entered form data.
        $data = Factory::getApplication()->getUserState(
            'com_guidedtours.edit.tour.data',
            []
        );

        if (empty($data)) {
            $data = $this->getItem();
        }

        return $data;
    }

    /**
     * Method to get a single record.
     *
     * @param   integer  $pk  The id of the primary key.
     *
     * @return  CMSObject|boolean  Object on success, false on failure.
     *
     * @since   4.3.0
     */
    public function getItem($pk = null)
    {
        Factory::getLanguage()->load('com_guidedtours.sys', JPATH_ADMINISTRATOR);

        $result = parent::getItem($pk);

        if (!empty($result->id)) {
            $result->title_translation       = Text::_($result->title);
            $result->description_translation = Text::_($result->description);
        }

        return $result;
    }

    /**
     * Delete all steps if a tour is deleted
     *
     * @param   object  $pks  The primary key related to the tours.
     *
     * @return  boolean
     *
     * @since   4.3.0
     */
    public function delete(&$pks)
    {
        $pks   = ArrayHelper::toInteger((array) $pks);
        $table = $this->getTable();

        // Include the plugins for the delete events.
        PluginHelper::importPlugin($this->events_map['delete']);

        // Iterate the items to delete each one.
        foreach ($pks as $i => $pk) {
            if ($table->load($pk)) {
                if ($this->canDelete($table)) {
                    $context = $this->option . '.' . $this->name;

                    // Trigger the before delete event.
                    $result = Factory::getApplication()->triggerEvent($this->event_before_delete, [$context, $table]);

                    if (\in_array(false, $result, true)) {
                        $this->setError($table->getError());

                        return false;
                    }

                    $tourId = $table->id;

                    if (!$table->delete($pk)) {
                        $this->setError($table->getError());

                        return false;
                    }

                    // Delete of the tour has been successful, now delete the steps
                    $db    = $this->getDatabase();
                    $query = $db->getQuery(true)
                        ->delete($db->quoteName('#__guidedtour_steps'))
                        ->where($db->quoteName('tour_id') . '=' . $tourId);
                    $db->setQuery($query);
                    $db->execute();

                    // Trigger the after event.
                    Factory::getApplication()->triggerEvent($this->event_after_delete, [$context, $table]);
                } else {
                    // Prune items that you can't change.
                    unset($pks[$i]);
                    $error = $this->getError();

                    if ($error) {
                        Log::add($error, Log::WARNING, 'jerror');

                        return false;
                    } else {
                        Log::add(Text::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'), Log::WARNING, 'jerror');

                        return false;
                    }
                }
            } else {
                $this->setError($table->getError());

                return false;
            }
        }

        // Clear the component's cache
        $this->cleanCache();

        return true;
    }

    /**
     * Duplicate all steps if a tour is duplicated
     *
     * @param   object  $pks  The primary key related to the tours.
     *
     * @return  boolean
     *
     * @since   4.3.0
     */
    public function duplicate(&$pks)
    {
        $user = $this->getCurrentUser();
        $db   = $this->getDatabase();

        // Access checks.
        if (!$user->authorise('core.create', 'com_guidedtours')) {
            throw new \Exception(Text::_('JERROR_CORE_CREATE_NOT_PERMITTED'));
        }

        $table = $this->getTable();

        $date = Factory::getDate()->toSql();

        foreach ($pks as $pk) {
            if ($table->load($pk, true)) {
                // Reset the id to create a new record.
                $table->id = 0;

                $table->published = 0;

                if (!$table->check() || !$table->store()) {
                    throw new \Exception($table->getError());
                }

                $pk = (int) $pk;

                $query = $db->getQuery(true)
                    ->select(
                        $db->quoteName(
                            [
                                'title',
                                'description',
                                'ordering',
                                'position',
                                'target',
                                'type',
                                'interactive_type',
                                'url',
                                'created',
                                'modified',
                                'checked_out_time',
                                'checked_out',
                                'language',
                                'note',
                            ]
                        )
                    )
                    ->from($db->quoteName('#__guidedtour_steps'))
                    ->where($db->quoteName('tour_id') . ' = :id')
                    ->bind(':id', $pk, ParameterType::INTEGER);

                $db->setQuery($query);
                $rows = $db->loadObjectList();

                if ($rows) {
                    $query = $db->getQuery(true)
                        ->insert($db->quoteName('#__guidedtour_steps'))
                        ->columns(
                            [
                                $db->quoteName('tour_id'),
                                $db->quoteName('title'),
                                $db->quoteName('description'),
                                $db->quoteName('ordering'),
                                $db->quoteName('position'),
                                $db->quoteName('target'),
                                $db->quoteName('type'),
                                $db->quoteName('interactive_type'),
                                $db->quoteName('url'),
                                $db->quoteName('created'),
                                $db->quoteName('created_by'),
                                $db->quoteName('modified'),
                                $db->quoteName('modified_by'),
                                $db->quoteName('language'),
                                $db->quoteName('note'),
                            ]
                        );

                    foreach ($rows as $step) {
                        $dataTypes = [
                            ParameterType::INTEGER,
                            ParameterType::STRING,
                            ParameterType::STRING,
                            ParameterType::INTEGER,
                            ParameterType::STRING,
                            ParameterType::STRING,
                            ParameterType::INTEGER,
                            ParameterType::INTEGER,
                            ParameterType::STRING,
                            ParameterType::STRING,
                            ParameterType::INTEGER,
                            ParameterType::STRING,
                            ParameterType::INTEGER,
                            ParameterType::STRING,
                            ParameterType::STRING,
                        ];

                        $query->values(
                            implode(
                                ',',
                                $query->bindArray(
                                    [
                                        $table->id,
                                        $step->title,
                                        $step->description,
                                        $step->ordering,
                                        $step->position,
                                        $step->target,
                                        $step->type,
                                        $step->interactive_type,
                                        $step->url,
                                        $date,
                                        $user->id,
                                        $date,
                                        $user->id,
                                        $step->language,
                                        $step->note,
                                    ],
                                    $dataTypes
                                )
                            )
                        );
                    }

                    $db->setQuery($query);

                    try {
                        $db->execute();
                    } catch (\RuntimeException $e) {
                        Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');

                        return false;
                    }
                }
            } else {
                throw new \Exception($table->getError());
            }
        }

        // Clear tours cache
        $this->cleanCache();

        return true;
    }

    /**
     * Sets a tour's steps language
     *
     * @param   int     $id        Id of a tour
     * @param   string  $language  The language to apply to the steps belong the tour
     *
     * @return  boolean
     *
     * @since  4.3.0
     */
    protected function setStepsLanguage(int $id, string $language = '*'): bool
    {
        if ($id <= 0) {
            return false;
        }

        $db    = $this->getDatabase();
        $query = $db->getQuery(true)
            ->update($db->quoteName('#__guidedtour_steps'))
            ->set($db->quoteName('language') . ' = :language')
            ->where($db->quoteName('tour_id') . ' = :tourId')
            ->bind(':language', $language)
            ->bind(':tourId', $id, ParameterType::INTEGER);

        return $db->setQuery($query)
            ->execute();
    }
}
