<?php

namespace Kirby\Cms;

use Kirby\Exception\InvalidArgumentException;
use Kirby\Toolkit\Str;
use Throwable;

/**
 * Represents a single block
 * which can be inspected further or
 * converted to HTML
 * @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 Block extends Item
{
	use HasMethods;

	public const ITEMS_CLASS = '\Kirby\Cms\Blocks';

	/**
	 * @var \Kirby\Cms\Content
	 */
	protected $content;

	/**
	 * @var bool
	 */
	protected $isHidden;

	/**
	 * Registry with all block models
	 *
	 * @var array
	 */
	public static $models = [];

	/**
	 * @var string
	 */
	protected $type;

	/**
	 * Proxy for content fields
	 *
	 * @param string $method
	 * @param array $args
	 * @return \Kirby\Cms\Field
	 */
	public function __call(string $method, array $args = [])
	{
		// block methods
		if ($this->hasMethod($method)) {
			return $this->callMethod($method, $args);
		}

		return $this->content()->get($method);
	}

	/**
	 * Creates a new block object
	 *
	 * @param array $params
	 * @throws \Kirby\Exception\InvalidArgumentException
	 */
	public function __construct(array $params)
	{
		parent::__construct($params);

		if (isset($params['type']) === false) {
			throw new InvalidArgumentException('The block type is missing');
		}

		// make sure the content is always defined as array to keep
		// at least a bit of backward compatibility with older fields
		if (is_array($params['content'] ?? null) === false) {
			$params['content'] = [];
		}

		$this->content  = $params['content'];
		$this->isHidden = $params['isHidden'] ?? false;
		$this->type     = $params['type'];

		// create the content object
		$this->content = new Content($this->content, $this->parent);
	}

	/**
	 * Converts the object to a string
	 *
	 * @return string
	 */
	public function __toString(): string
	{
		return $this->toHtml();
	}

	/**
	 * Returns the content object
	 *
	 * @return \Kirby\Cms\Content
	 */
	public function content()
	{
		return $this->content;
	}

	/**
	 * Controller for the block snippet
	 *
	 * @return array
	 */
	public function controller(): array
	{
		return [
			'block'   => $this,
			'content' => $this->content(),
			// deprecated block data
			'data'    => $this,
			'id'      => $this->id(),
			'prev'    => $this->prev(),
			'next'    => $this->next()
		];
	}

	/**
	 * Converts the block 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);
	}

	/**
	 * Constructs a block object with registering blocks models
	 *
	 * @param array $params
	 * @return static
	 * @throws \Kirby\Exception\InvalidArgumentException
	 * @internal
	 */
	public static function factory(array $params)
	{
		$type = $params['type'] ?? null;

		if (empty($type) === false && $class = (static::$models[$type] ?? null)) {
			$object = new $class($params);

			if (is_a($object, 'Kirby\Cms\Block') === true) {
				return $object;
			}
		}

		// default model for blocks
		if ($class = (static::$models['Kirby\Cms\Block'] ?? null)) {
			$object = new $class($params);

			if (is_a($object, 'Kirby\Cms\Block') === true) {
				return $object;
			}
		}

		return new static($params);
	}

	/**
	 * Checks if the block is empty
	 *
	 * @return bool
	 */
	public function isEmpty(): bool
	{
		return empty($this->content()->toArray());
	}

	/**
	 * Checks if the block is hidden
	 * from being rendered in the frontend
	 *
	 * @return bool
	 */
	public function isHidden(): bool
	{
		return $this->isHidden;
	}

	/**
	 * Checks if the block is not empty
	 *
	 * @return bool
	 */
	public function isNotEmpty(): bool
	{
		return $this->isEmpty() === false;
	}

	/**
	 * Returns the sibling collection that filtered by block status
	 *
	 * @return \Kirby\Cms\Collection
	 */
	protected function siblingsCollection()
	{
		return $this->siblings->filter('isHidden', $this->isHidden());
	}

	/**
	 * Returns the block type
	 *
	 * @return string
	 */
	public function type(): string
	{
		return $this->type;
	}

	/**
	 * The result is being sent to the editor
	 * via the API in the panel
	 *
	 * @return array
	 */
	public function toArray(): array
	{
		return [
			'content'  => $this->content()->toArray(),
			'id'       => $this->id(),
			'isHidden' => $this->isHidden(),
			'type'     => $this->type(),
		];
	}

	/**
	 * Converts the block to html first
	 * and then places that inside a field
	 * object. This can be used further
	 * with all available field methods
	 *
	 * @return \Kirby\Cms\Field
	 */
	public function toField()
	{
		return new Field($this->parent(), $this->id(), $this->toHtml());
	}

	/**
	 * Converts the block to HTML
	 *
	 * @return string
	 */
	public function toHtml(): string
	{
		try {
			$kirby = $this->parent()->kirby();
			return (string)$kirby->snippet('blocks/' . $this->type(), $this->controller(), true);
		} catch (Throwable $e) {
			if ($kirby->option('debug') === true) {
				return '<p>Block error: "' . $e->getMessage() . '" in block type: "' . $this->type() . '"</p>';
			}

			return '';
		}
	}
}
