<?php

declare(strict_types=1);

namespace mirzaev\huesos\controllers;

// Files of the project
use mirzaev\huesos\controllers\core,
	mirzaev\huesos\models\enumerations\language,
	mirzaev\huesos\models\entry,
	mirzaev\huesos\models\category,
	mirzaev\huesos\models\product,
	mirzaev\huesos\models\cart,
	mirzaev\huesos\models\menu;

// Framework for PHP
use mirzaev\minimal\http\enumerations\content,
	mirzaev\minimal\http\enumerations\protocol,
	mirzaev\minimal\http\request;

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

/**
 * Controller of catalog
 *
 * @package mirzaev\huesos\controllers
 *
 * @param cart|null $cart Instance of the cart
 * @param array $errors Registry of errors
 *
 * @method null index(?string $product, ?string $category, ?string $brand, ?string $sort, ?string $text) Catalog
 *
 * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License
 * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
 */
final class catalog extends core
{
	/**
	 * Cart
	 *
	 * @var model|null $cart Instance of the cart
	 */
	protected readonly ?cart $cart;

	/**
	 * Errors
	 *
	 * @var array $errors Registry of errors
	 */
	protected array $errors = [
		'session' => [],
		'account' => [],
		'menu' => [],
		'catalog' => []
	];

	/**
	 * Catalog
	 *
	 * Browsing the catalog and receiving product data at the same time
	 *
	 * Receiving product data is necessary so that you can simultaneously open the product card at the desired location when opening the page
	 *
	 * @param string|null $product Product identifier (&product=1)
	 * @param string|null $category Category identifier (&category=1)
	 * @param string|null $brand Brand name (&brand=mirzaev)
	 * @param string|null $sort Sort typ (&sort=cost)
	 * @param string|null $text Search text (&text=zaloopa)
	 *
	 * @return null
	 */
	public function index(
		?string $product = null,
		?string $category = null,
		?string $brand = null,
		?string $sort = null,
		?string $text = null
	): null {
		// Initializing the cart
		$this->cart ??= $this->account?->cart() ?? $this->session?->cart();

		// Initializing the cart data
		$this->view->cart = [
			'products' => $this->cart?->products(language: $this->language, currency: $this->currency)
		];

		// Validating received product identifier
		if (isset($product) && preg_match('/[\d]+/', $product, $matches)) $product = (int) $matches[0];
		else unset($product);

		if (isset($product)) {
			// Received and validated product identifier

			// Search for the product data and write to the buffer of global variables of view templater
			$this->view->product = product::read(
				filter: "d.identifier == @identifier && d.deleted != true && d.hidden != true",
				sort: 'd.created DESC',
				amount: 1,
				return: '{_id: d._id, identifier: d.identifier, name: d.name.@language, description: d.description.@language, cost: d.cost.@currency, weight: d.weight, dimensions: d.dimensions, brand: d.brand.@language, compatibility: d.compatibility.@language, cost: d.cost.@currency, images: {"200": d.images[*].storage["200"], "800": d.images[*].storage["800"]}}',
				language: $this->language,
				currency: $this->currency,
				parameters: ['identifier' => $product],
				errors: $this->errors['catalog']
			);

			// This is only for generate product card @todo need to move that in backed templated
			if (!empty($this->view->product)) {
				// Initialized the product

				// Writing data about being in the cart
				$this->view->product = [
					'cart' => [
						'amount' => $this->view->cart['products'][$this->view->product->getId()]['amount'] ?? 0,
						'text' => [
							'add' => $this->language === language::ru ? 'Добавить в корзину' : 'Add to the cart',
							'added' => $this->language === language::ru ? 'Добавлено в корзину' : 'Added to the cart'
						]
					]
				] + $this->view->product->getAll();
			}
		}

		// Intializing buffer of query parameters
		$parameters = [];

		// Initializing buffer of filters query (AQL)
		$_filters = 'd.deleted != true && d.hidden != true';

		// Validating received brand name
		if (isset($brand) && preg_match('/[\w\s]+/u', urldecode($brand), $matches)) $brand = $matches[0];
		else unset($brand);

		if (isset($brand)) {
			// Received and validated filter by brand

			// Writing to the buffer of filters query (AQL)
			$_filters .= ' && d.brand.@language == @brand';

			// Writing to the buffer of query parameters
			$parameters['brand'] = $brand;
		}

		// Writing to the session buffer
		$this->core->request(new request('PATCH', '/session/write', protocol::http_3, parameters: ['catalog_filters_brand' => $brand ?? null]));

		// Writing to the account buffer
		$this->core->request(new request('PATCH', '/account/write', protocol::http_3, parameters: ['catalog_filters_brand' => $brand ?? null]));

		// Writing to the current implementator of the buffer
		$this->session->buffer = ['catalog' => ['filters' => ['brand' => $brand ?? null]]] + ($this->session->buffer ?? []);

		// Initialize buffer of filters query (AQL)
		$_sort = 'd.position ASC, d.name ASC, d.created DESC';

		// Validating received sort
		if (isset($sort) && preg_match('/[\w\s]+/u', $sort, $matches)) $sort = $matches[0];
		else unset($sort);

		if (isset($sort)) {
			// Received and validated sort

			// Write to the buffer of sort query (AQL)
			$_sort = "d.@sort DESC, $_sort";

			// Write to the buffer of query parameters
			$parameters['sort'] = $sort;
		}

		// Writing to the session buffer
		$this->core->request(new request('PATCH', '/session/write', protocol::http_3, parameters: ['catalog_sort' => $sort ?? null]));

		// Writing to the account buffer
		$this->core->request(new request('PATCH', '/account/write', protocol::http_3, parameters: ['catalog_sort' => $sort ?? null]));

		// Validating
		if (isset($category) && preg_match('/[\d]+/', $category, $matches)) $category = (int) $matches[0];
		else unset($category);

		if (isset($category)) {
			// Received and validated identifier of the category 

			// Initialize of category
			$category = category::_read('d.identifier == @identifier', parameters: ['identifier' => $category], errors: $this->errors['catalog']);

			if ($category instanceof category) {
				// Found the category

				// Write to the response buffer
				$response['category'] = ['name' => $category->name ?? null];

				// Search for categories that are descendants of $category
				$entries = entry::search(
					document: $category,
					amount: 50,
					categories_merge: 'name: v.name.@language',
					/* products_merge: 'DISTINCT MERGE(d, {name: d.name.@language, description: d.description.@language, compatibility: d.compatibility.@language, brand: d.brand.@language, cost: d.cost.@currency})', */
					parameters: ['language' => $this->language->name],
					errors: $this->errors['catalog']
				);

				// Initialize buffers of entries (in singular, by parameter from ArangoDB)
				$category = $product = [];

				foreach ($entries as $entry) {
					// Iterate over entries (descendands)

					// Write entry to the buffer of entries (sort by $category and $product)
					${$entry->_type}[] = $entry;
				}

				// Write to the buffer of global variables of view templater
				$this->view->categories = $category;

				// Write to the buffer of global variables of view templater
				$this->view->products = $product;

				if (isset($this->view->products) && count($this->view->products) > 0) {
					// Amount of rendered products is more than 0				

					// Write to the buffer of filters query (AQL)
					$_filters .= ' && POSITION(["' . implode('", "', array_map(fn(_document $document): string => $document->getId(), $this->view->products)) . '"], d._id)';
				}

				// Deleting buffers
				unset($category, $product);
			}
		} else if (!isset($category)) {
			// Not received identifier of the category

			// search for root ascendants categories
			$this->view->categories = entry::ascendants(
				descendant: new category,
				return: 'DISTINCT MERGE(ascendant, { name: ascendant.name.@language})',
				parameters: ['language' => $this->language->name],
				errors: $this->errors['catalog']
			) ?? null;
		}

		// Validating @todo add throwing errors
		if (isset($text) && preg_match('/[\w\s]+/u', urldecode($text), $matches) && mb_strlen($matches[0]) > 2) $text = $matches[0];
		else unset($text);

		// Writing to the session buffer
		$this->core->request(new request('PATCH', '/session/write', protocol::http_3, parameters: ['catalog_search_text' => $text ?? null]));

		// Writing to the account buffer
		$this->core->request(new request('PATCH', '/account/write', protocol::http_3, parameters: ['catalog_search_text' => $text ?? null]));

		if (isset($brand) || isset($text) || (isset($this->view->products) && count($this->view->products) > 0)) {
			// Received and validated at least one of filters or amount of rendered products is more than 0

			// Search for filters and write to the buffer of global variables of view templater
			$this->view->filters = [
				'brands' => product::collect(
					return: 'd.brand.@language',
					products: array_map(fn(_document $document): string => $document->getId(), $this->view->products ?? []),
					language: $this->language,
					errors: $this->errors['catalog']
				)
			];
		}

		// Search among products in the $category
		if (isset($text) || isset($this->view->products) && count($this->view->products) > 0) {
			// Amount of rendered products is more than 0

			// Search for products and write to the buffer of global variables of view templater
			$this->view->products = product::read(
				search: $text ?? null,
				filter: $_filters,
				sort: $_sort,
				amount: 50, // @todo pagination
				return: 'DISTINCT MERGE(d, {name: d.name.@language, description: d.description.@language, compatibility: d.compatibility.@language, brand: d.brand.@language, cost: d.cost.@currency})',
				language: $this->language,
				currency: $this->currency,
				parameters: $parameters,
				errors: $this->errors['catalog']
			);
		}

		if (isset($menu)) {
			// 

		} else {
			// Not received ... menu

			// Search for filters and write to the buffer of global variables of view templater
			$this->view->menu = menu::_read(
				return: 'MERGE(d, { name: d.name.@language })',
				sort: 'd.style.order ASC, d.created DESC, d._key DESC',
				amount: 4,
				parameters: ['language' => $this->language->name],
				errors: $this->errors['menu']
			);

			// Universalizing
			if ($this->view->menu instanceof _document) $this->view->menu = [$this->view->menu];
		}

		if (str_contains($this->request->headers['accept'], content::json->value)) {
			// Request for JSON response

			// Initializing the response body buffer
			$body = [
				'title' => $this->language === language::ru ? 'Каталог' : 'Catalog' // @see https://git.mirzaev.sexy/mirzaev/huesos/issues/1
			];

			if (isset($this->view->categories)) {
				// Initialized categories

				// Render HTML-code of categories and write to the response body buffer
				$body['categories'] = $this->view->render('catalog/elements/categories.html');
			}

			if (isset($this->view->product)) {
				// Initialized product

				// Writing data of the product to the response body buffer @todo GENERATE THIS ON THE SERVER
				$body['product'] = $this->view->product;
			}

			if (isset($this->view->products)) {
				// Initialized products

				// Render HTML-code of products and write to the response body buffer
				$body['products'] = $this->view->render('catalog/elements/products.html');
			}

			if (isset($this->view->filters)) {
				// Initialized filters

				// Render HTML-code of filters and write to the response body buffer
				$body['filters'] = $this->view->render('catalog/elements/filters.html');
			}

			// Sending response
			$this->response
				->start()
				->clean()
				->sse()
				->json($body + ['errors' => $this->errors])
				->validate($this->request)
				?->body()
				->end();

			// Deinitializing the response body buffer
			unset($body);
		} else if (str_contains($this->request->headers['accept'], content::any->value)) {
			// Request for any response

			if (!empty($this->view->product)) {
				// Initialized the product data

				// Writing javascript code that has been executed after core and damper has been loaded
				$this->view->javascript = [
					sprintf(
						<<<javascript
						if (typeof _window === 'undefined') {
							_window = setTimeout(() => core.catalog.product.system('%s'), 500);
						}
						javascript,
						$this->view->product['identifier']
					)
				] + ($this->view->javascript ?? []);
			}

			// Render page
			$page = $this->view->render('catalog/page.html', [
				'h2' => $this->language === language::ru ? 'Каталог' : 'Catalog' // @see https://git.mirzaev.sexy/mirzaev/huesos/issues/1
			]);

			// Sending response
			$this->response
				->start()
				->clean()
				->sse()
				->write($page)
				->validate($this->request)
				?->body()
				->end();

			// Deinitializing rendered page
			unset($page);
		}

		// Exit (success/fail)
		return null;
	}
}
