<?php
/**
 * @package   DPCalendar
 * @copyright Copyright (C) 2014 Digital Peak GmbH. <https://www.digital-peak.com>
 * @license   https://www.gnu.org/licenses/gpl-3.0.html GNU/GPL
 */

namespace DigitalPeak\Component\DPCalendar\Site\Service;

\defined('_JEXEC') or die();

use DigitalPeak\Component\DPCalendar\Administrator\Calendar\CalendarInterface;
use DigitalPeak\Component\DPCalendar\Administrator\Router\Rules\DPCalendarRules;
use Joomla\CMS\Application\SiteApplication;
use Joomla\CMS\Categories\CategoryFactoryInterface;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Component\Router\RouterView;
use Joomla\CMS\Component\Router\RouterViewConfiguration;
use Joomla\CMS\Component\Router\Rules\NomenuRules;
use Joomla\CMS\Component\Router\Rules\StandardRules;
use Joomla\CMS\Menu\AbstractMenu;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Database\DatabaseAwareTrait;
use Joomla\Database\DatabaseInterface;

class Router extends RouterView
{
	use DatabaseAwareTrait;

	public function __construct(SiteApplication $app, AbstractMenu $menu, private CategoryFactoryInterface $categoryFactory, DatabaseInterface $db)
	{
		ComponentHelper::getParams('com_dpcalendar');

		$calendar = new RouterViewConfiguration('calendar');
		$calendar->setKey('ids');
		$this->registerView($calendar);

		$list = new RouterViewConfiguration('list');
		$list->setKey('ids');
		$list->addLayout('blog');
		$this->registerView($list);

		$map = new RouterViewConfiguration('map');
		$map->setKey('ids');
		$this->registerView($map);

		$event = new RouterViewConfiguration('event');
		$event->setKey('id');
		$event->setParent($calendar, 'calid');
		$this->registerView($event);

		$form = new RouterViewConfiguration('form');
		$form->setKey('calid');
		$this->registerView($form);

		$bookings = new RouterViewConfiguration('bookings');
		$this->registerView($bookings);

		$tickets = new RouterViewConfiguration('tickets');
		$this->registerView($tickets);

		$ticket = new RouterViewConfiguration('ticket');
		$ticket->setKey('uid');
		$ticket->setParent($tickets, 'id');
		$this->registerView($ticket);

		$locations = new RouterViewConfiguration('locations');
		$locations->setKey('ids');
		$this->registerView($locations);

		$location = new RouterViewConfiguration('location');
		$location->setKey('id');
		$location->setParent($locations, 'id');
		$this->registerView($location);

		$form = new RouterViewConfiguration('locationform');
		$form->setKey('l_id');
		$this->registerView($form);

		$profile = new RouterViewConfiguration('profile');
		$this->registerView($profile);

		parent::__construct($app, $menu);

		$this->setDatabase($db);

		$this->attachRule(new DPCalendarRules($this));
		$this->attachRule(new StandardRules($this));
		$this->attachRule(new NomenuRules($this));
	}

	public function build(&$query)
	{
		$this->updateEventParentForQuery($query);

		$active = $this->app->getInput()->get('view');

		// When active is event details view, it should act as parent of the tickets and bookings view
		if ($active === 'event' && !empty($query['view']) && \in_array($query['view'], ['tickets', 'bookings'])) {
			$this->views[$query['view']]->setParent($this->views['event'], 'e_id');
		}

		return parent::build($query);
	}

	public function parse(&$segments)
	{
		$active = $this->menu->getActive();

		// Set the active as parent for the event view when it contains calendars
		if (\count($segments) === 1 && !empty($active->query['view']) && \in_array($active->query['view'], ['calendar', 'list', 'map'])) {
			$this->views['event']->setParent($this->views[$active->query['view']], 'calid');
		}

		return parent::parse($segments);
	}

	private function updateEventParentForQuery(array $query): void
	{
		// Check if the query is an event view
		if (empty($query['view']) || $query['view'] != 'event' || empty($query['Itemid'])) {
			return;
		}

		// It should have already a correct Itemid
		$items = $this->menu->getItems('id', $query['Itemid']);
		if (!$items) {
			return;
		}

		// The item
		$item = reset($items);

		// Check if the item can fit as parent
		if (!\in_array($item->query['view'], ['calendar', 'list', 'map'])) {
			return;
		}

		// Set the active the the current active parent if applicable
		$this->views['event']->setParent($this->views[$item->query['view']], 'calid');
	}

	public function getCalendarSegment(string $id): array
	{
		$category = $this->categoryFactory->createCategory()->get($id);

		if ($category === null) {
			return [];
		}

		$path    = array_reverse($category->getPath(), true);
		$path[0] = '1:root';

		foreach ($path as &$segment) {
			[$id, $segment] = explode(':', (string)$segment, 2);
		}

		return $path;
	}

	public function getMapSegment(string $id): array
	{
		return $this->getCalendarSegment($id);
	}

	public function getListSegment(string $id): array
	{
		return $this->getCalendarSegment($id);
	}

	public function getEventSegment(string $id, array $query): array
	{
		// Hack when no menu item is available for the NomenuRules
		if (empty($query['Itemid'])) {
			return [0 => $id];
		}

		// Return the id when an external event, except when the id is numeric, then it is database caching
		if (!empty($query['calid']) && !is_numeric($query['calid']) && !is_numeric($id)) {
			return [0 => $id];
		}

		if (str_starts_with($id, ':') || !str_contains($id, ':')) {
			$db      = $this->getDatabase();
			$dbquery = $db->getQuery(true);
			$dbquery->select('alias')
				->from('#__dpcalendar_events')
				->where('id = ' . $db->quote($id));
			$db->setQuery($dbquery);

			$id .= ':' . $db->loadResult();
		}

		[$void, $segment] = explode(':', $id, 2);

		return [$void => $segment];
	}

	public function getFormSegment(string $id, array $query): array
	{
		return $this->getEventSegment($id, $query);
	}

	public function getTicketSegment(string $uid): array
	{
		if (is_numeric($uid)) {
			$db      = $this->getDatabase();
			$dbquery = $db->getQuery(true);
			$dbquery->select('uid')
				->from('#__dpcalendar_tickets')
				->where('id = ' . (int)$uid);
			$db->setQuery($dbquery);
			$uid = $db->loadResult();
		}

		return [$uid => $uid];
	}

	public function getLocationSegment(string $id, array $query): array
	{
		// Hack when no menu item is available for the NomenuRules
		if (empty($query['Itemid'])) {
			return [0 => $id];
		}

		if (str_starts_with($id, ':') || !str_contains($id, ':')) {
			$db      = $this->getDatabase();
			$dbquery = $db->getQuery(true);
			$dbquery->select('alias')
				->from('#__dpcalendar_locations')
				->where('id = ' . $db->quote($id));
			$db->setQuery($dbquery);

			$id .= ':' . $db->loadResult();
		}

		[$void, $segment] = explode(':', $id, 2);

		return [$void => $segment];
	}

	public function getLocationsSegment(string $id): array
	{
		if (str_starts_with($id, ':') || !str_contains($id, ':')) {
			$db      = $this->getDatabase();
			$dbquery = $db->getQuery(true);
			$dbquery->select('alias')
				->from('#__dpcalendar_locations')
				->where('id = ' . $db->quote($id));
			$db->setQuery($dbquery);

			$id .= ':' . $db->loadResult();
		}

		[$void, $segment] = explode(':', $id, 2);

		return [$void => $segment];
	}

	public function getLocationformSegment(string $id, array $query): array
	{
		return $this->getLocationSegment($id, $query);
	}

	public function getCalendarId(string $segment, array $query): string
	{
		if (isset($query['id'])) {
			$category = $this->categoryFactory->createCategory(['access' => false])->get($query['id']);

			if ($category !== null) {
				foreach ($category->getChildren() as $child) {
					if ($child->alias == $segment) {
						return (string)$child->id;
					}
				}
			}
		}

		return '';
	}

	public function getListId(string $segment, array $query): string
	{
		return $this->getCalendarId($segment, $query);
	}

	public function getMapId(string $segment, array $query): string
	{
		return $this->getCalendarId($segment, $query);
	}

	public function getEventId(string $segment): string
	{
		$calIds = [];

		// Get the active calendars
		if (($active = $this->menu->getActive()) !== null) {
			$calIds = $active->getParams()->get('ids');
		}

		// Load also the external calendars when all are fetched
		if ((is_countable($calIds) ? \count($calIds) : 0) === 1 && $calIds[0] == -1) {
			// Fetch external calendars
			PluginHelper::importPlugin('dpcalendar');
			$tmp = $this->app->triggerEvent('onCalendarsFetch');
			if (!empty($tmp)) {
				foreach ($tmp as $tmpCalendars) {
					foreach ($tmpCalendars as $calendar) {
						$calIds[] = $calendar->getId();
					}
				}
			}
		}

		foreach ($calIds as $calId) {
			// If the event belongs to the external calendar, return the segment as it is the id
			if (is_numeric($calId) || !str_starts_with($segment, (string)$calId)) {
				continue;
			}

			return $segment;
		}

		$db      = $this->getDatabase();
		$dbquery = $db->getQuery(true);
		$dbquery->select('id')
			->from('#__dpcalendar_events')
			->where('(alias = ' . $db->quote($segment) . (is_numeric($segment) ? ' or id = ' . (int)$segment : '') . ')');

		if ($calIds && !\in_array('-1', $calIds)) {
			// Loop over the calids, they can be string as with DB cache of external events
			$condition = 'catid in (';
			foreach ($calIds as $calId) {
				$condition .= $db->quote($calId) . ',';

				$cal = $this->app->bootComponent('dpcalendar')->getMVCFactory()->createModel('Calendar', 'Administrator')->getCalendar($calId);
				if (!$cal instanceof CalendarInterface) {
					continue;
				}

				foreach ($cal->getChildren(true) as $child) {
					$condition .= $db->quote($child->getId()) . ',';
				}
			}
			$dbquery->where(trim($condition, ',') . ')');
		}

		// Set the query on the database instance
		$db->setQuery($dbquery);

		// Get the result
		$result = $db->loadResult();

		// Can be an external event when there is no menu item for the calendar
		if (empty($result) && !$calIds) {
			return $segment;
		}

		// Return the result
		return $result ?: '';
	}

	public function getTicketId(string $segment): string
	{
		// An uid
		if (!is_numeric($segment)) {
			return $segment;
		}

		$db      = $this->getDatabase();
		$dbquery = $db->getQuery(true);
		$dbquery->select('uid')
			->from('#__dpcalendar_tickets')
			->where('id = ' . (int)$segment);
		$db->setQuery($dbquery);

		return $db->loadResult() ?: '';
	}

	public function getLocationId(string $segment): int
	{
		$db      = $this->getDatabase();
		$dbquery = $db->getQuery(true);
		$dbquery->select('id')
			->from('#__dpcalendar_locations')
			->where('(alias = ' . $db->quote($segment) . (is_numeric($segment) ? ' or id = ' . (int)$segment : '') . ')');
		$db->setQuery($dbquery);

		return (int)$db->loadResult();
	}
}
