<?php

/**
 * Joomla! Content Management System
 *
 * @copyright  (C) 2017 Open Source Matters, Inc. <https://www.joomla.org>
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Categories;

use Joomla\CMS\Factory;
use Joomla\CMS\Object\LegacyErrorHandlingTrait;
use Joomla\CMS\Object\LegacyPropertyManagementTrait;
use Joomla\CMS\Tree\NodeInterface;
use Joomla\CMS\Tree\NodeTrait;
use Joomla\Registry\Registry;

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

/**
 * Helper class to load Categorytree
 *
 * @since  1.6
 */
#[\AllowDynamicProperties]
class CategoryNode implements NodeInterface
{
    use LegacyErrorHandlingTrait;
    use LegacyPropertyManagementTrait;
    use NodeTrait;

    /**
     * Primary key
     *
     * @var    integer
     * @since  1.6
     */
    public $id;

    /**
     * The id of the category in the asset table
     *
     * @var    integer
     * @since  1.6
     */
    public $asset_id;

    /**
     * The id of the parent of category in the asset table, 0 for category root
     *
     * @var    integer
     * @since  1.6
     */
    public $parent_id;

    /**
     * The lft value for this category in the category tree
     *
     * @var    integer
     * @since  1.6
     */
    public $lft;

    /**
     * The rgt value for this category in the category tree
     *
     * @var    integer
     * @since  1.6
     */
    public $rgt;

    /**
     * The depth of this category's position in the category tree
     *
     * @var    integer
     * @since  1.6
     */
    public $level;

    /**
     * The extension this category is associated with
     *
     * @var    integer
     * @since  1.6
     */
    public $extension;

    /**
     * The menu title for the category (a short name)
     *
     * @var    string
     * @since  1.6
     */
    public $title;

    /**
     * The alias for the category
     *
     * @var    string
     * @since  1.6
     */
    public $alias;

    /**
     * Description of the category.
     *
     * @var    string
     * @since  1.6
     */
    public $description;

    /**
     * The publication status of the category
     *
     * @var    boolean
     * @since  1.6
     */
    public $published;

    /**
     * Whether the category is or is not checked out
     *
     * @var    boolean
     * @since  1.6
     */
    public $checked_out;

    /**
     * The time at which the category was checked out
     *
     * @var    string
     * @since  1.6
     */
    public $checked_out_time;

    /**
     * Access level for the category
     *
     * @var    integer
     * @since  1.6
     */
    public $access;

    /**
     * JSON string of parameters
     *
     * @var    string
     * @since  1.6
     */
    public $params;

    /**
     * Metadata description
     *
     * @var    string
     * @since  1.6
     */
    public $metadesc;

    /**
     * Keywords for metadata
     *
     * @var    string
     * @since  1.6
     */
    public $metakey;

    /**
     * JSON string of other metadata
     *
     * @var    string
     * @since  1.6
     */
    public $metadata;

    /**
     * The ID of the user who created the category
     *
     * @var    integer
     * @since  1.6
     */
    public $created_user_id;

    /**
     * The time at which the category was created
     *
     * @var    string
     * @since  1.6
     */
    public $created_time;

    /**
     * The ID of the user who last modified the category
     *
     * @var    integer
     * @since  1.6
     */
    public $modified_user_id;

    /**
     * The time at which the category was modified
     *
     * @var    string
     * @since  1.6
     */
    public $modified_time;

    /**
     * Number of times the category has been viewed
     *
     * @var    integer
     * @since  1.6
     */
    public $hits;

    /**
     * The language for the category in xx-XX format
     *
     * @var    string
     * @since  1.6
     */
    public $language;

    /**
     * Number of items in this category or descendants of this category
     *
     * @var    integer
     * @since  1.6
     */
    public $numitems;

    /**
     * Slug for the category (used in URL)
     *
     * @var    string
     * @since  1.6
     */
    public $slug;

    /**
     * Array of  assets
     *
     * @var    array
     * @since  1.6
     */
    public $assets;

    /**
     * Path from root to this category
     *
     * @var    array
     * @since  1.6
     */
    protected $_path = [];

    /**
     * Flag if all children have been loaded
     *
     * @var    boolean
     * @since  1.6
     */
    protected $_allChildrenloaded = false;

    /**
     * Constructor of this tree
     *
     * @var    Categories
     * @since  1.6
     */
    protected $_constructor;

    /**
     * Class constructor
     *
     * @param   array       $category     The category data.
     * @param   Categories  $constructor  The tree constructor.
     *
     * @since   1.6
     */
    public function __construct($category = null, $constructor = null)
    {
        if ($category) {
            foreach ($category as $key => $value) {
                $this->$key = $value;
            }

            if ($constructor) {
                $this->_constructor = $constructor;
            }
        }
    }

    /**
     * Set the parent of this category
     *
     * If the category already has a parent, the link is unset
     *
     * @param   NodeInterface  $parent  CategoryNode for the parent to be set or null
     *
     * @return  void
     *
     * @since   1.6
     */
    public function setParent(NodeInterface $parent)
    {
        if (!\is_null($this->_parent)) {
            $key = array_search($this, $this->_parent->_children);
            unset($this->_parent->_children[$key]);
        }

        $this->_parent = $parent;

        $this->_parent->_children[] = &$this;

        if (\count($this->_parent->_children) > 1) {
            end($this->_parent->_children);
            $this->_leftSibling                = prev($this->_parent->_children);
            $this->_leftSibling->_rightsibling = &$this;
        }

        if ($this->parent_id != 1) {
            $this->_path = $parent->getPath();
        }

        $this->_path[$this->id] = $this->id . ':' . $this->alias;
    }

    /**
     * Get the children of this node
     *
     * @param   boolean  $recursive  False by default
     *
     * @return  CategoryNode[]  The children
     *
     * @since   1.6
     */
    public function &getChildren($recursive = false)
    {
        if (!$this->_allChildrenloaded) {
            $temp = $this->_constructor->get($this->id, true);

            if ($temp) {
                $this->_children     = $temp->getChildren();
                $this->_leftSibling  = $temp->getSibling(false);
                $this->_rightSibling = $temp->getSibling(true);
                $this->setAllLoaded();
            }
        }

        if ($recursive) {
            $items = [];

            foreach ($this->_children as $child) {
                $items[] = $child;
                $items   = array_merge($items, $child->getChildren(true));
            }

            return $items;
        }

        return $this->_children;
    }

    /**
     * Returns the right or left sibling of a category
     *
     * @param   boolean  $right  If set to false, returns the left sibling
     *
     * @return  CategoryNode|null  CategoryNode object with the sibling information or null if there is no sibling on that side.
     *
     * @since   1.6
     */
    public function getSibling($right = true)
    {
        if (!$this->_allChildrenloaded) {
            $temp                = $this->_constructor->get($this->id, true);
            $this->_children     = $temp->getChildren();
            $this->_leftSibling  = $temp->getSibling(false);
            $this->_rightSibling = $temp->getSibling(true);
            $this->setAllLoaded();
        }

        if ($right) {
            return $this->_rightSibling;
        }

        return $this->_leftSibling;
    }

    /**
     * Returns the category parameters
     *
     * @return  Registry
     *
     * @since   1.6
     */
    public function getParams()
    {
        if (!($this->params instanceof Registry)) {
            $this->params = new Registry($this->params);
        }

        return $this->params;
    }

    /**
     * Returns the category metadata
     *
     * @return  Registry  A Registry object containing the metadata
     *
     * @since   1.6
     */
    public function getMetadata()
    {
        if (!($this->metadata instanceof Registry)) {
            $this->metadata = new Registry($this->metadata);
        }

        return $this->metadata;
    }

    /**
     * Returns the category path to the root category
     *
     * @return  array
     *
     * @since   1.6
     */
    public function getPath()
    {
        return $this->_path;
    }

    /**
     * Returns the user that created the category
     *
     * @param   boolean  $modifiedUser  Returns the modified_user when set to true
     *
     * @return  \Joomla\CMS\User\User  A User object containing a userid
     *
     * @since   1.6
     */
    public function getAuthor($modifiedUser = false)
    {
        if ($modifiedUser) {
            return Factory::getUser($this->modified_user_id);
        }

        return Factory::getUser($this->created_user_id);
    }

    /**
     * Set to load all children
     *
     * @return  void
     *
     * @since   1.6
     */
    public function setAllLoaded()
    {
        $this->_allChildrenloaded = true;

        foreach ($this->_children as $child) {
            $child->setAllLoaded();
        }
    }

    /**
     * Returns the number of items.
     *
     * @param   boolean  $recursive  If false number of children, if true number of descendants
     *
     * @return  integer  Number of children or descendants
     *
     * @since   1.6
     */
    public function getNumItems($recursive = false)
    {
        if ($recursive) {
            $count = $this->numitems;

            foreach ($this->getChildren() as $child) {
                $count += $child->getNumItems(true);
            }

            return $count;
        }

        return $this->numitems;
    }

    /**
     * Serialize the node.
     *
     * @since   4.3.2
     */
    public function __serialize()
    {
        $vars = get_object_vars($this);

        // Store constructor as array of options.
        if ($this->_constructor) {
            $vars['_constructor'] = $this->_constructor->getOptions();
        }

        return $vars;
    }

    /**
     * Unserialize the node.
     *
     * @param   array  $data
     *
     * @since   4.3.2
     */
    public function __unserialize($data)
    {
        foreach ($data as $key => $value) {
            $this->$key = $value;
        }

        // Restore constructor from array of options.
        if ($this->_constructor) {
            $this->_constructor = Categories::getInstance($this->_constructor['extension'], $this->_constructor);
        }
    }
}
