<?php
declare(strict_types=1);

/**
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 * @link          https://cakephp.org CakePHP(tm) Project
 * @since         3.0.0
 * @license       https://opensource.org/licenses/mit-license.php MIT License
 */
namespace Cake\Collection\Iterator;

use Cake\Collection\CollectionInterface;
use Cake\Collection\CollectionTrait;
use RecursiveIterator;
use RecursiveIteratorIterator;

/**
 * A Recursive iterator used to flatten nested structures and also exposes
 * all Collection methods
 */
class TreeIterator extends RecursiveIteratorIterator implements CollectionInterface
{
    use CollectionTrait;

    /**
     * The iteration mode
     *
     * @var int
     */
    protected $_mode;

    /**
     * Constructor
     *
     * @param \RecursiveIterator $items The iterator to flatten.
     * @param int $mode Iterator mode.
     * @param int $flags Iterator flags.
     */
    public function __construct(
        RecursiveIterator $items,
        int $mode = RecursiveIteratorIterator::SELF_FIRST,
        int $flags = 0
    ) {
        parent::__construct($items, $mode, $flags);
        $this->_mode = $mode;
    }

    /**
     * Returns another iterator which will return the values ready to be displayed
     * to a user. It does so by extracting one property from each of the elements
     * and prefixing it with a spacer so that the relative position in the tree
     * can be visualized.
     *
     * Both $valuePath and $keyPath can be a string with a property name to extract
     * or a dot separated path of properties that should be followed to get the last
     * one in the path.
     *
     * Alternatively, $valuePath and $keyPath can be callable functions. They will get
     * the current element as first parameter, the current iteration key as second
     * parameter, and the iterator instance as third argument.
     *
     * ### Example
     *
     * ```
     *  $printer = (new Collection($treeStructure))->listNested()->printer('name');
     * ```
     *
     * Using a closure:
     *
     * ```
     *  $printer = (new Collection($treeStructure))
     *      ->listNested()
     *      ->printer(function ($item, $key, $iterator) {
     *          return $item->name;
     *      });
     * ```
     *
     * @param string|callable $valuePath The property to extract or a callable to return
     * the display value
     * @param string|callable|null $keyPath The property to use as iteration key or a
     * callable returning the key value.
     * @param string $spacer The string to use for prefixing the values according to
     * their depth in the tree
     * @return \Cake\Collection\Iterator\TreePrinter
     */
    public function printer($valuePath, $keyPath = null, $spacer = '__')
    {
        if (!$keyPath) {
            $counter = 0;
            $keyPath = function () use (&$counter) {
                return $counter++;
            };
        }

        return new TreePrinter(
            $this->getInnerIterator(),
            $valuePath,
            $keyPath,
            $spacer,
            $this->_mode
        );
    }
}
