<?php
namespace Bake\View\Helper;

use Cake\ORM\Association;
use Cake\View\Helper;

/**
 * DocBlock helper
 */
class DocBlockHelper extends Helper
{
    /**
     * Converts an entity class type to its DocBlock hint type counterpart.
     *
     * @param string $type The entity class type (a fully qualified class name).
     * @param \Cake\ORM\Association $association The association related to the entity class.
     * @return string The DocBlock type
     */
    public function associatedEntityTypeToHintType($type, Association $association)
    {
        if ($association->type() === Association::MANY_TO_MANY ||
            $association->type() === Association::ONE_TO_MANY
        ) {
            return $type . '[]';
        }
        return $type;
    }

    /**
     * Builds a map of entity property names and DocBlock types for use
     * in generating `@property` hints.
     *
     * This method expects a property schema as generated by
     * `\Bake\Shell\Task\ModelTask::getEntityPropertySchema()`.
     *
     * The generated map has the format of
     *
     * ```
     * [
     *     'property-name' => 'doc-block-type',
     *     ...
     * ]
     * ```
     *
     * @see \Bake\Shell\Task\ModelTask::getEntityPropertySchema
     *
     * @param array $propertySchema The property schema to use for generating the type map.
     * @return array The property DocType map.
     */
    public function buildEntityPropertyHintTypeMap(array $propertySchema)
    {
        $properties = [];
        foreach ($propertySchema as $property => $info) {
            switch ($info['kind']) {
                case 'column':
                    $properties[$property] = $this->columnTypeToHintType($info['type']);
                    break;

                case 'association':
                    $type = $this->associatedEntityTypeToHintType($info['type'], $info['association']);
                    if ($info['association']->type() === Association::MANY_TO_ONE) {
                        $properties = $this->_insertAfter(
                            $properties,
                            $info['association']->foreignKey(),
                            [$property => $type]
                        );
                    } else {
                        $properties[$property] = $type;
                    }
                    break;
            }
        }
        return $properties;
    }

    /**
     * Converts a column type to its DocBlock type counterpart.
     *
     * This method only supports the default CakePHP column types,
     * custom column/database types will be ignored.
     *
     * @see \Cake\Database\Type
     *
     * @param string $type The column type.
     * @return null|string The DocBlock type, or `null` for unsupported column types.
     */
    public function columnTypeToHintType($type)
    {
        switch ($type) {
            case 'string':
            case 'text':
            case 'uuid':
                return 'string';

            case 'integer':
            case 'biginteger':
                return 'int';

            case 'float':
            case 'decimal':
                return 'float';

            case 'boolean':
                return 'bool';

            case 'binary':
                return 'string|resource';

            case 'date':
            case 'datetime':
            case 'time':
            case 'timestamp':
                return '\Cake\I18n\Time';
        }

        return null;
    }

    /**
     * Inserts a value after a specific key in an associative array.
     *
     * In case the given key cannot be found, the value will be appended.
     *
     * @param array $target The array in which to insert the new value.
     * @param string $key The array key after which to insert the new value.
     * @param mixed $value The entry to insert.
     * @return array The array with the new value inserted.
     */
    protected function _insertAfter(array $target, $key, $value)
    {
        $index = array_search($key, array_keys($target));
        if ($index !== false) {
            $target = array_merge(
                array_slice($target, 0, $index + 1),
                $value,
                array_slice($target, $index + 1, null)
            );
        } else {
            $target += (array)$value;
        }
        return $target;
    }
}
