<?php

namespace Kirby\Cms;

use Exception;
use Kirby\Data\Json;
use Kirby\Data\Yaml;
use Kirby\Parsley\Parsley;
use Kirby\Parsley\Schema\Blocks as BlockSchema;
use Kirby\Toolkit\A;
use Kirby\Toolkit\Str;
use Throwable;

/**
 * A collection of blocks
 * @since 3.5.0
 *
 * @package   Kirby Cms
 * @author    Bastian Allgeier <bastian@getkirby.com>
 * @link      https://getkirby.com
 * @copyright Bastian Allgeier
 * @license   https://getkirby.com/license
 */
class Blocks extends Items
{
    public const ITEM_CLASS = '\Kirby\Cms\Block';

    /**
     * Return HTML when the collection is
     * converted to a string
     *
     * @return string
     */
    public function __toString(): string
    {
        return $this->toHtml();
    }

    /**
     * Converts the blocks to HTML and then
     * uses the Str::excerpt method to create
     * a non-formatted, shortened excerpt from it
     *
     * @param mixed ...$args
     * @return string
     */
    public function excerpt(...$args)
    {
        return Str::excerpt($this->toHtml(), ...$args);
    }

    /**
     * Wrapper around the factory to
     * catch blocks from layouts
     *
     * @param array $items
     * @param array $params
     * @return \Kirby\Cms\Blocks
     */
    public static function factory(array $items = null, array $params = [])
    {
        $items = static::extractFromLayouts($items);
        $items = BlockConverter::editorBlocks($items);

        return parent::factory($items, $params);
    }

    /**
     * Pull out blocks from layouts
     *
     * @param array $input
     * @return array
     */
    protected static function extractFromLayouts(array $input): array
    {
        if (empty($input) === true) {
            return [];
        }

        if (
            // no columns = no layout
            array_key_exists('columns', $input[0]) === false ||
            // checks if this is a block for the builder plugin
            array_key_exists('_key', $input[0]) === true
        ) {
            return $input;
        }

        $blocks = [];

        foreach ($input as $layout) {
            foreach (($layout['columns'] ?? []) as $column) {
                foreach (($column['blocks'] ?? []) as $block) {
                    $blocks[] = $block;
                }
            }
        }

        return $blocks;
    }

    /**
     * Checks if a given block type exists in the collection
     * @since 3.6.0
     *
     * @param string $type
     * @return bool
     */
    public function hasType(string $type): bool
    {
        return $this->filterBy('type', $type)->count() > 0;
    }

    /**
     * Parse and sanitize various block formats
     *
     * @param array|string $input
     * @return array
     */
    public static function parse($input): array
    {
        if (empty($input) === false && is_array($input) === false) {
            try {
                $input = Json::decode((string)$input);
            } catch (Throwable $e) {
                try {
                    // try to import the old YAML format
                    $yaml  = Yaml::decode((string)$input);
                    $first = A::first($yaml);

                    // check for valid yaml
                    if (empty($yaml) === true || (isset($first['_key']) === false && isset($first['type']) === false)) {
                        throw new Exception('Invalid YAML');
                    } else {
                        $input = $yaml;
                    }
                } catch (Throwable $e) {
                    $parser = new Parsley((string)$input, new BlockSchema());
                    $input  = $parser->blocks();
                }
            }
        }

        if (empty($input) === true) {
            return [];
        }

        return $input;
    }

    /**
     * Convert all blocks to HTML
     *
     * @return string
     */
    public function toHtml(): string
    {
        $html = [];

        foreach ($this->data as $block) {
            $html[] = $block->toHtml();
        }

        return implode($html);
    }
}
