<?php

declare(strict_types=1);

namespace mirzaev\ebala\models;

// Файлы проекта
use mirzaev\ebala\models\traits\status;

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

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

// System libraries
use datetime,
	exception;

/**
 * Модель заданий
 *
 * @package mirzaev\ebala\models
 * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
 */
final class task extends core
{
	use status;

	/**
	 * Collection name in ArangoDB
	 */
	final public const COLLECTION = 'task';

	/**
	 * Инстанция документа в базе данных 
	 */
	protected readonly _document $document;

	/**
	 * Create task in ArangoDB
	 *
	 * @param string|int|null $date
	 * @param ?string $worker
	 * @param ?string $work
	 * @param ?string $start
	 * @param ?string $end
	 * @param ?string $market
	 * @param bool $confirmed
	 * @param bool $published
	 * @param bool $hided
	 * @param bool $problematic
	 * @param bool $completed
	 * @param ?string $commentary
	 * @param array $errors
	 *
	 * @return ?string Identificator of instance of ArangoDB
	 */
	public static function create(
		string|int|null $date = null,
		?string $worker = null,
		?string $work = null,
		?string $start = null,
		?string $end = null,
		?string $market = null,
		bool $confirmed = false,
		bool $published = false,
		bool $hided = false,
		bool $problematic = false,
		bool $completed = false,
		?string $commentary = null,
		array &$errors = []
	): ?string {
		try {
			if (
				collection::init(static::$arangodb->session, self::COLLECTION)
				&& collection::init(static::$arangodb->session, worker::COLLECTION)
				&& collection::init(static::$arangodb->session, market::COLLECTION)
			) {
				// Инициализированы коллекции

				// Запись документа в базу данны и возврат (успех)
				return document::write(static::$arangodb->session, self::COLLECTION, [
					'date' => (int) ($date ?? strtotime('+1 day')),
					'worker' => $worker,
					'work' => $work,
					'start' => $start,
					'end' => $end,
					'market' => $market,
					'confirmed' => $confirmed,
					'published' => $published,
					'hided' => $hided,
					'problematic' => $problematic,
					'completed' => $completed,
					'commentary' => $commentary,
				]);
			} else throw new exception('Не удалось инициализировать коллекции');
		} catch (exception $e) {
			// Write to the errors registry
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return null;
	}

	/**
	 * Read tasks from ArangoDB
	 *
	 * @param ?string $before Injection of AQL-code before search of worker and market
	 * @param ?string $after Injection of AQL-code after search of worker and market
	 * @param int $amount Amount of tasks
	 * @param int $page Offset by amount
	 * @param string $target Collection or view name
	 * @param array $binds Binds for query
	 * @param array $errors Errors registry
	 *
	 * @return array Instances from ArangoDB
	 *
	 * @TODO
	 * 1. Remake task.worker with task.market to separate documents (edges) (not important)
	 */
	public static function list(
		?string $before = '',
		?string $after = '',
		int $amount = 100,
		int $page = 1,
		string $sort = 'task.date DESC, task.created DESC, task._key DESC',
		string $target = self::COLLECTION,
		array $binds = [],
		array &$errors = []
	): array {
		try {
			if (
				collection::init(static::$arangodb->session, self::COLLECTION)
				&& collection::init(static::$arangodb->session, worker::COLLECTION)
				&& collection::init(static::$arangodb->session, market::COLLECTION)
			) {
				// Инициализированы коллекции

				// Search the session data in ArangoDB with VIEW (LET можно заменить на поиск по рёбрам но тут и так сойдёт)
				$tasks = collection::search(static::$arangodb->session, sprintf(
					<<<AQL
						FOR task IN %s
							%s
							LET worker = (FOR worker in %s FILTER worker.id != null && worker.id LIKE task.worker SORT worker.created DESC, worker.id DESC  LIMIT 1 RETURN worker)[0]
							LET market = (FOR market in %s FILTER market.id != null && market.id LIKE task.market SORT market.created DESC, market.id DESC LIMIT 1 RETURN market)[0]
							%s
							SORT %s
							LIMIT %d, %d
							RETURN {task, worker, market}
					AQL,
					$target,
					$before,
					worker::COLLECTION,
					market::COLLECTION,
					$after,
					$sort,
					--$page <= 0 ? 0 : $amount * $page,
					$amount
				), $binds);

				// Exit (success)
				return empty($tasks) ? [] : (is_array($tasks) ? $tasks : [$tasks]);
			} else throw new exception('Не удалось инициализировать коллекции');
		} catch (exception $e) {
			// Write to the errors registry
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return [];
	}

	/**
	 * Посчитать количество часов работы
	 *
	 * @param string $start Начало работы (H:i)
	 * @param string $end Конец работы (H:i)
	 * @param array $errors Errors registry
	 *
	 * @return ?float Количество часов, если удалось расчитать
	 */
	public static function hours(string $start, string $end, array &$errors = []): ?float
	{
		try {
			if (
				!empty($start = datetime::createFromFormat('H:i', (string) $start)) && $start instanceof datetime
				&& !empty($end = datetime::createFromFormat('H:i', (string) $end)) && $end instanceof datetime
			) {
				// Инициализированы $start и $end

				// Расчёт часов работы
				$hours = (float) $start->diff($end)->format('%R%H.%i');
				if ($hours < 0) $hours += 24;
				if ($hours >= 6.5 && $hours < 9) $hours -= 0.5;
				else if ($hours >= 9 && $hours < 12.5) $hours -= 1;
				else if ($hours >= 12.5) $hours -= 1.5;

				// Выход (успех)
				return $hours;
			}
		} catch (exception $e) {
			// Write to the errors registry
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Выход (провал)
		return null;
	}

	/**
	 * Generate work type label in Russian
	 *
	 * @param string $work Type of work
	 *
	 * @return string
	 */
	public static function label(string $work): string
	{
		return match (mb_strtolower($work)) {
			'cashiers', 'cashier', 'кассиры', 'кассир' => 'Кассир',
			'displayers', 'displayer', 'выкладчики', 'выкладчик' => 'Выкладчик',
			'gastronomes', 'gastronome', 'гастрономы', 'гастроном' => 'Гастроном',
			'brigadiers', 'brigadier', 'бригадиры', 'бригадир' => 'Бригадир',
			'loaders', 'loader', 'грузчики', 'грузчик' => 'Грузчик',
			'loaders_mobile', 'loader_mobile', 'мобильные грузчики', 'мобильный грузчик' => 'Мобильный грузчик',
			'universals_mobile', 'universal_mobile', 'мобильные универсалы', 'мобильный универсал' => 'Мобильный универсал',
			default => $work
		};
	}

	/**
	 * Create a transaction for work on a task
	 *
	 * @param string $task
	 * @param string $worker
	 * @param int $amount
	 * @param array $errors
	 *
	 * @return ?string Identificator of instance of ArangoDB
	 */
	public static function transaction(
		string $task,
		string $worker,
		int $amount = 0,
		array &$errors = []
	): ?string {
		try {
			if (
				collection::init(static::$arangodb->session, self::COLLECTION)
				&& collection::init(static::$arangodb->session, worker::COLLECTION)
				&& collection::init(static::$arangodb->session, 'transaction', true)
			) {
				// Инициализированы коллекции

				// Запись документа в базу данны и возврат (успех)
				return document::write(static::$arangodb->session, 'transaction', [
					'_from' => $task,
					'_to' => $worker,
					'amount' => $amount,
					'processed' => 0,
				]);
			} else throw new exception('Не удалось инициализировать коллекции');
		} catch (exception $e) {
			// Write to the errors registry
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return null;
	}

}
