<?php

declare(strict_types=1);

namespace mirzaev\huesos\models;

// Files of the project
use mirzaev\huesos\models\core,
	mirzaev\huesos\models\traits\document as document_trait,
	mirzaev\huesos\models\interfaces\document as document_interface,
	mirzaev\huesos\models\interfaces\collection as collection_interface,
	mirzaev\huesos\models\enumerations\language,
	mirzaev\huesos\models\enumerations\currency;

// Framework for ArangoDB
use mirzaev\arangodb\collection,
	mirzaev\arangodb\document;

// Library for ArangoDB
use ArangoDBClient\Document as _document;

// Built-in libraries
use exception;

/**
 * Model of a product
 *
 * @package mirzaev\huesos\models
 *
 * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
 * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
 */
final class product extends core implements document_interface, collection_interface
{
	use document_trait;

	/**
	 * Name of the collection in ArangoDB
	 */
	final public const string COLLECTION = 'product';

	/**
	 * Write a product
	 *
	 * @param int $identifier Identifier (unique)
	 * @param array $name Name [['en' => value], ['ru' => значение]]
	 * @param array|null $description Description [['en' => value], ['ru' => значение]]
	 * @param float $cost Cost
	 * @param float $weight Weight
	 * @param array $dimensions Dimensions ['x' => 0.0, 'y' => 0.0, 'z' => 0.0]
	 * @param array|null $brand Brand [['en' => value], ['ru' => значение]]
	 * @param array|null $compatibility Compatibility [['en' => value], ['ru' => значение]]
	 * @param array $images Images (first will be thumbnail)
	 * @param int|null $position Position for sorting in the catalog (ASC)
	 * @param array $data Data
	 * @param array &$errors Registry of errors
	 *
	 * @return string|null Identifier (_id) of instance of the product document in ArangoDB, if created
	 *
	 * @todo
	 * 1. Bind parameters
	 */
	public static function write(
		int $identifier,
		array $name = [['en' => 'ERROR']],
		?array $description = [['en' => 'ERROR']],
		array $cost = [['usd' => 0]],
		float $weight = 0,
		array $dimensions = ['x' => 0, 'y' => 0, 'z' => 0],
		?array $brand = [['en' => 'ERROR']],
		?array $compatibility = [['en' => 'ERROR']],
		array $images = [],
		?int $position = null,
		array $data = [],
		array &$errors = []
	): string|null {
		try {
			if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
				// Initialized the collection

				// Writing in ArangoDB and exit (success)
				return document::write(
					static::COLLECTION,
					[
						'identifier' => $identifier,
						'name' => $name,
						'description' => $description,
						'cost' => $cost ?? 0,
						'weight' => $weight ?? 0,
						'dimensions' => [
							'x' => $dimensions['x'] ?? 0,
							'y' => $dimensions['y'] ?? 0,
							'z' => $dimensions['z'] ?? 0,
						],
						'brand' => $brand,
						'compatibility' => $compatibility,
						'images' => $images,
						'position' => $position,
						'version' => ROBOT_VERSION
					] + $data,
					errors: $errors
				);
			} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
		} catch (exception $e) {
			// Writing to the registry of errors
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return null;
	}

	/**
	 * Read products
	 *
	 * @param string|null $search Search (text)
	 * @param string|null $filter Flter (AQL)
	 * @param string|null $sort Sort (AQL)
	 * @param int $page Page
	 * @param int $amount Amount per page
	 * @param string|null $return Return (AQL)
	 * @param language|null $language Language
	 * @param currency|null $currency Currency
	 * @param array $parameters Binded parameters for placeholders ['placeholder' => parameter]
	 * @param array &$errors Registry of errors
	 *
	 * @return array|static Found products or instance of the product from ArangoDB (can be empty)
	 *
	 * @todo убрать language и currency
	 */
	public static function read(
		?string $search = null,
		?string $filter = 'd.deleted != true && d.hidden != true',
		?string $sort = 'd.position ASC, d.created DESC',
		int $page = 1,
		int $amount = 100,
		?string $return = 'DISTINCT d',
		?language $language = null,
		?currency $currency = null,
		array $parameters = [],
		array &$errors = []
	): array|static {
		try {
			if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
				// Initialized the collection

				// Initializing of the language parameter
				if ($language instanceof language) $parameters['language'] = $language->name;

				// Initializing of the currency parameter
				if ($currency instanceof currency) $parameters['currency'] = $currency->name;

				// Initializing parameters for search
				if ($search) $parameters += [
					'search' => $search,
					'analyzer' => 'text_' . $language->name ?? language::en->name
				];

				// Search for products
				$result = collection::execute(
					sprintf(
						<<<'AQL'
						FOR d IN @@collection %s
							%s
							%s 
							LIMIT @offset, @amount
							RETURN %s
						AQL,
						empty($search) ? '' :	<<<'AQL'
						SEARCH 
							LEVENSHTEIN_MATCH(
								d.name.@language,
								TOKENS(@search, @analyzer)[0],
								1,
								false
							) OR
							LEVENSHTEIN_MATCH(
								d.description.@language,
								TOKENS(@search, @analyzer)[0],
								1,
								false
							) OR
							LEVENSHTEIN_MATCH(
								d.compatibility.@language,
								TOKENS(@search, @analyzer)[0],
								1,
								false
							)
						AQL,
						empty($filter) ? '' : "FILTER $filter",
						empty($search) ? (empty($sort) ? '' : "SORT $sort") : (empty($sort) ? "SORT BM25(d) DESC" : "SORT BM25(d) DESC, $sort"),
						empty($return) ? 'DISTINCT d' : $return
					),
					[
						'@collection' => empty($search) ? static::COLLECTION : static::COLLECTION . 's_search',
						'offset' => --$page <= 0 ? $page = 0 : $page * $amount,
						'amount' => $amount,
					] + $parameters,
					errors: $errors
				);

				if ($amount === 1 && $result instanceof _document) {
					// Found the product @todo need to rebuild this

					// Initializing the object
					$product = new static;

					if (method_exists($product, '__document')) {
						// Object can implement a document from ArangoDB

						// Writing the instance of product document from ArangoDB to the implement object
						$product->__document($result);

						// Exit (success)
						return $product;
					} else throw new exception('Class ' . static::class . ' does not implement a document from ArangoDB');
				} else if (!empty($result)) {
					// Found products

					// Exit (success)
					return is_array($result) ? $result : [$result];
				}
			} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
		} catch (exception $e) {
			// Writing to the registry of errors
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return [];
	}

	/**
	 * Collect parameter from all products
	 *
	 * @param string $return Return (AQL path)
	 * @param array $products Array with products system identifiers ["_id", "_id", "_id"...]
	 * @param array &$errors Registry of errors
	 *
	 * @return array Array with found unique parameter values from all products (can be empty)
	 */
	public static function collect(
		string $return = 'd._key',
		array $products = [],
		language $language = language::en,
		array $parameters = [],
		array &$errors = []
	): array {
		try {
			if (collection::initialize(static::COLLECTION, static::TYPE, errors: $errors)) {
				// Initialized the collection

				if ($result = collection::execute(
					sprintf(
						<<<'AQL'
						FOR d IN @@collection 
							%s
							RETURN DISTINCT %s
						AQL,
						empty($products) ? '' : 'FILTER POSITION(["' . implode('", "', $products) . '"], d._id)',
						empty($return) ? 'd._key' : $return
					),
					[
						'@collection' => static::COLLECTION,
						'language' => $language->name,
					] + $parameters,
					errors: $errors
				)) {
					// Found parameters

					// Exit (success)
					return is_array($result) ? $result : [$result];
				} else return [];
			} else throw new exception('Failed to initialize ' . static::TYPE . ' collection: ' . static::COLLECTION);
		} catch (exception $e) {
			// Writing to the registry of errors
			$errors[] = [
				'text' => $e->getMessage(),
				'file' => $e->getFile(),
				'line' => $e->getLine(),
				'stack' => $e->getTrace()
			];
		}

		// Exit (fail)
		return [];
	}
}
