<?php

declare(strict_types=1);

namespace mirzaev\ebala\models;

// Фреймворк PHP
use mirzaev\minimal\model;

// Фреймворк ArangoDB
use mirzaev\arangodb\connection as arangodb,
	mirzaev\arangodb\collection,
	mirzaev\arangodb\document;

// Библиотека для ArangoDB
use ArangoDBClient\Document as _document,
	ArangoDBClient\DocumentHandler as _document_handler;

// Встроенные библиотеки
use exception;

/**
 * Ядро моделей
 *
 * @package mirzaev\ebala\models
 * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
 */
class core extends model
{
	/**
	 * Постфикс
	 */
	final public const POSTFIX = '';

	/**
	 * Путь до файла с настройками подключения к базе данных ArangoDB
	 */
	final public const ARANGODB = '../settings/arangodb.php';

	/**
	 * Соединение с базой данных ArangoDB
	 */
	protected static arangodb $arangodb;

	/**
	 * Коллекция
	 */
	public const COLLECTION = 'THIS_COLLECTION_SHOULD_NOT_EXIST';

	/**
	 * Конструктор
	 *
	 * @param bool $initialize Инициализировать модель?
	 * @param ?arangodb $arangodb Инстанция соединения с базой данных ArangoDB
	 */
	public function __construct(bool $initialize = true, ?arangodb $arangodb = null)
	{
		parent::__construct($initialize);

		if ($initialize) {
			// Запрошена инициализация

			if (isset($arangodb)) {
				// Получена инстанция соединения с базой данных

				// Запись и инициализация соединения с базой данных
				$this->__set('arangodb', $arangodb);
			} else {
				// Не получена инстанция соединения с базой данных

				// Инициализация соединения с базой данных по умолчанию
				$this->__get('arangodb');
			}
		}
	}

	/**
	 * Read from ArangoDB
	 *
	 * @param string $filter Выражения для фильтрации на языке AQL
	 * @param string $sort Выражение для сортировки на языке AQL
	 * @param int $amount Количество документов для выборки
	 * @param int $page Страница
	 * @param string $return Выражение описываемое возвращаемые данные на языке AQL
	 * @param array &$errors Реестр ошибок
	 *
	 * @return _document|array|null Массив инстанций документов в базе данных, если найдены
	 */
	public static function read(
		string $filter = '',
		string $sort = 'd.created DESC, d._key DESC',
		int $amount = 1,
		int $page = 1,
		string $return = 'd',
		array &$errors = []
	): _document|array|null {
		try {
			if (collection::init(static::$arangodb->session, static::COLLECTION)) {
				// Инициализирована коллекция

				// Exit (success)
				return collection::search(
					static::$arangodb->session,
					sprintf(
						<<<'AQL'
							FOR d IN %s
								%s
								%s
								LIMIT %d, %d
								RETURN %s
						AQL,
						static::COLLECTION,
						empty($filter) ? '' : "FILTER $filter",
						empty($sort) ? '' : "SORT $sort",
						--$page <= 0 ? 0 : $amount * $page,
						$amount,
						$return
					)
				);
			} else throw new exception('Не удалось инициализировать коллекцию');
		} catch (exception $e) {
			// Запись в реестр ошибок
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return null;
	}

	/**
	 * Collect from ArangoDB
	 *
	 * @param string $filter Выражения для фильтрации на языке AQL
	 * @param string $sort Выражение для сортировки на языке AQL
	 * @param int $amount Количество документов для выборки
	 * @param int $page Страница
	 * @param string $index Параметр по которому будет производиться сборка
	 * @param string $return Выражение описываемое возвращаемые данные на языке AQL
	 * @param array &$errors Реестр ошибок
	 *
	 * @return _document|array|null Массив инстанций документов в базе данных, если найдены
	 */
	public static function collect(
		string $filter = '',
		string $sort = 'd.created DESC, d._key DESC',
		int $amount = 1,
		int $page = 1,
		string $index = 'd.updated',
		string $return = 'd',
		array &$errors = []
	): _document|array|null {
		try {
			if (collection::init(static::$arangodb->session, static::COLLECTION)) {
				// Инициализирована коллекция

				// Exit (success)
				return collection::search(
					static::$arangodb->session,
					sprintf(
						<<<'AQL'
							FOR d IN %s
								%s
								%s
								LIMIT %d, %d
								COLLECT index = %s INTO group = %s
								RETURN {  [index]: group }
						AQL,
						static::COLLECTION,
						empty($filter) ? '' : "FILTER $filter",
						empty($sort) ? '' : "SORT $sort",
						--$page <= 0 ? 0 : $amount * $page,
						$amount,
						$index,
						$return
					)
				);
			} else throw new exception('Не удалось инициализировать коллекцию');
		} catch (exception $e) {
			// Запись в реестр ошибок
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return null;
	}


	/**
	 * Count documents in ArangoDB
	 *
	 * @param ?string $collection Коллекция для подсчёта
	 * @param array &$errors Реестр ошибок
	 *
	 * @return int|null Количество документов в базе данных, если найдены
	 */
	public static function count(?string $collection = null, array &$errors = []): int|null {
		try {
			if (collection::init(static::$arangodb->session, static::COLLECTION)) {
				// Инициализирована коллекция

				// Exit (success)
				return collection::search(
					static::$arangodb->session,
					sprintf(
						<<<'AQL'
							RETURN LENGTH(%s)
						AQL,
						$collection ?? static::COLLECTION
					)
				);
			} else throw new exception('Не удалось инициализировать коллекцию');
		} catch (exception $e) {
			// Запись в реестр ошибок
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return null;
	}

	/**
	 * Generate identifier
	 *
	 * @param array &$errors Реестр ошибок
	 *
	 * @return int Идентиикатор (свободный)
	 */
	public static function id(array &$errors = []): int
	{
		try {
			if (collection::init(static::$arangodb->session, static::COLLECTION)) {
				// Инициализирована коллекция

				// Exit (success)
				return collection::search(
					static::$arangodb->session,
					sprintf(
						<<<'AQL'
							RETURN MAX((FOR d in %s RETURN +d.id))
						AQL,
						$collection ?? static::COLLECTION
					)
				) + 1;
			} else throw new exception('Не удалось инициализировать коллекцию');
		} catch (exception $e) {
			// Запись в реестр ошибок
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return 0;
	}

	/**
	 * Delete from ArangoDB
	 *
	 * @param _document $instance Instance of the document in ArangoDB
	 * @param array &$errors Реестр ошибок
	 *
	 * @return bool Deleted without errors?
	 */
	public static function delete(_document $instance, array &$errors = []): bool
	{
		try {
			if (collection::init(static::$arangodb->session, static::COLLECTION)) {
				// Инициализирована коллекция

				return (new _document_handler(static::$arangodb->session))->remove($instance);
			} else throw new exception('Не удалось инициализировать коллекцию');
		} catch (exception $e) {
			// Запись в реестр ошибок
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return false;
	}

	/**
	 * Обновить инстанцию в базе данных
	 *
	 * Да поебать мне
	 *
	 * @param _document $instance Инстанция документа ArangoDB
	 *
	 * @return bool Записано в базу данных?
	 */
	public static function update(_document $instance): bool
	{
		return document::update(static::$arangodb->session, $instance);
	}

	/**
	 * Записать свойство
	 *
	 * @param string $name Название
	 * @param mixed $value Значение
	 */
	public function __set(string $name, mixed $value = null): void
	{
		match ($name) {
			'arangodb' => (function () use ($value) {
				if ($this->__isset('arangodb')) {
					// Свойство уже было инициализировано

					// Выброс исключения (неудача)
					throw new exception('Запрещено реинициализировать соединение с базой данных ArangoDB ($this::$arangodb)', 500);
				} else {
					// Свойство ещё не было инициализировано

					if ($value instanceof arangodb) {
						// Передано подходящее значение

						// Запись свойства (успех)
						self::$arangodb = $value;
					} else {
						// Передано неподходящее значение

						// Выброс исключения (неудача)
						throw new exception('Соединение с базой данных ArangoDB ($this::$arangodb) должно быть инстанцией mirzaev\arangodb\connection', 500);
					}
				}
			})(),
			default => parent::__set($name, $value)
		};
	}

	/**
	 * Прочитать свойство
	 *
	 * @param string $name Название
	 *
	 * @return mixed Содержимое
	 */
	public function __get(string $name): mixed
	{
		return match ($name) {
			'arangodb' => (function () {
				try {
					if (!$this->__isset('arangodb')) {
						// Свойство не инициализировано

						// Инициализация значения по умолчанию исходя из настроек
						$this->__set('arangodb', new arangodb(require static::ARANGODB));
					}

					return self::$arangodb;
				} catch (exception) {
					return null;
				}
			})(),
			default => parent::__get($name)
		};
	}

	/**
	 * Проверить свойство на инициализированность
	 *
	 * @param string $name Название
	 */
	public function __isset(string $name): bool
	{
		return parent::__isset($name);
	}

	/**
	 * Удалить свойство
	 *
	 * @param string $name Название
	 */
	public function __unset(string $name): void
	{
		parent::__unset($name);
	}

	/**
	 * Статический вызов
	 *
	 * @param string $name Название
	 * @param array $arguments Параметры
	 */
	public static function __callStatic(string $name, array $arguments): mixed
	{
		match ($name) {
			'arangodb' => (new static)->__get('arangodb'),
			default => throw new exception("Не найдено свойство или функция: $name", 500)
		};
	}
}
