<?php

namespace App\Classes;

use Klein\Klein;

use App\Classes\Db;

use App\Model\SectionModel;
use App\Model\PageModel;

class Router {

	protected static $router;
	protected static $routes = [];

	public static function _init(): void {
		self::$router = new Klein();
	}

	//-------------------------------------//

	/**
	 * Load all routes into the Klein object
	 *
	 * @return void
	 */
	public static function fire(): void
	{
		$path = parse_url($_SERVER['REQUEST_URI'])['path'];
		$check = str_replace('.', '', $path);

		// If it's a dynamic file or the file doesn't exist, go through the router.
		if ($path == $check || !file_exists(getcwd() . $path)) {

			Db::load();
			self::setDefaultLayout();
			self::loadConfigRoutes();
			self::loadDbRoutes();

			// Process basic routes
			foreach (self::$routes as $route) {
				// If route does not match the base url
				if ($route[0] != $path) {
					continue;
				}

				// ["/example/my-page", "ExampleController", "action", "" : ["view", "title", "description"]],
				self::addBasicRoute(['GET', 'POST'], $route);

				break;
			}

			self::createNavigation();
			self::setHttpError();

			self::$router->dispatch();
		}
	}

	/**
	 * Add CRUD routes
	 * Example usage: Router::resource('/example', 'CrudController');
	 *
	 * @param string $route The URL location
	 * @param string $controller Controller to handle this route
	 *
	 * @return void
	 */
	public static function resource(string $route, string $controller): void
	{
		/*
		 * HTTP Verb    Part (URL)          Action (Method)
		 *
		 * GET          /route              indexAction
		 * GET          /route/create       createAction
		 * POST         /route              storeAction
		 * GET          /route/{id}         showAction
		 * GET          /route/{id}/edit    editAction
		 * PUT/PATCH    /route/{id}         updateAction
		 * DELETE       /route/{id}         destroyAction
		 */

		self::addRoute(['GET'], [$route, $controller, 'indexAction']);
		self::addRoute(['GET'], [$route . '/create', $controller, 'createAction']);
		self::addRoute(['POST'], [$route, $controller, 'storeAction']);
		self::addRoute(['GET'], [$route . '/[i:id]', $controller, 'showAction', ['id']]);
		self::addRoute(['GET'], [$route . '/[i:id]/edit', $controller, 'editAction', ['id']]);
		self::addRoute(['PUT', 'PATCH'], [$route . '/[i:id]', $controller, 'updateAction', ['id']]);
		self::addRoute(['DELETE'], [$route . '/[i:id]', $controller, 'destroyAction', ['id']]);
	}

	//-------------------------------------//

	protected static function setDefaultLayout(): void
	{
		self::$router->respond(function ($request, $response, $service) {
			$service->layout('../app/views/layouts/default.php');
		});
	}

	protected static function loadConfigRoutes(): void
	{
		if (file_exists('../route.php')) {
			self::$routes = require_once '../route.php';
		}
	}

	/**
	 * Add all pages in the Db to self::$routes
	 *
	 * @return void
	 */
	protected static function loadDbRoutes(): void
	{
		// Load all sections from Db
		$sections = SectionModel::selectAll('*', 'WHERE `active` = 1 ORDER BY `order` ASC');

		// Return if no sections
		if (!_exists($sections)) {
			return;
		}

		// Load all pages from Db
		$pages = PageModel::selectAll('DISTINCT page.*', '
			LEFT JOIN page_has_content ON page_has_content.page_id = page.id
			LEFT JOIN content ON content.id = page_has_content.content_id
			WHERE page.active = 1 AND content.active = 1
			ORDER BY page.order ASC;
		');

		// Return if no pages
		if (!_exists($pages)) {
			return;
		}

		// Select id column
		$section = array_column($sections, 'section', 'id');

		// Loop through all pages
		foreach ($pages as $pageKey => $page) {
			// Skip if section isn't created / active
			if (!_exists($section, $page['section_id'])) { continue; }

			// url = /section/page
			$url = '/' . $section[$page['section_id']] . '/' . $page['page'];

			// Add route
			self::$routes[] = [$url, 'PageController', 'route', $page['id']];
		}

		// Cache sections and pages
		Db::setSections($sections);
		Db::setPages($pages);
	}

	protected static function addRoute(array $method = [], array $data = []): void
	{
		if (!_exists($method) || !_exists($data)) {
			return;
		}

		$route      = $data[0] ?? '';
		$controller = $data[1] ?? '';
		$action     = $data[2] ?? '';
		$param      = $data[3] ?? [];
		if ($route == '' || $controller == '' || $action == '') {
			return;
		}

		// Create Klein route
		self::$router->respond($method, $route, function($request, $response, $service)
			use($controller, $action, $param) {

			// Create new Controller object
			$controller = '\App\Controllers\\' . $controller;
			$controller = new $controller(self::$router);

			$stillValid = true;

			// If method does not exist in object
			if (!method_exists($controller, $action)) {
				$stillValid = false;
			}

			// If no valid permissions
			if ($controller->getAdminSection() &&
				$controller->getLoggedIn() == false) {
				$stillValid = false;
			}

			// Call Controller action
			if ($stillValid) {

				// Loop through params
				$params = [];
				foreach ($param as $name) {
					$params[] = $request->param($name);
				}

				return $controller->{$action}(...$params);
			}
			else {
				$controller->throw404();
			}
		});
	}

	protected static function addBasicRoute(array $method = [], array $route = []): void
	{
		if (!_exists($method) || !_exists($route)) {
			return;
		}

		// Create Klein route
		self::$router->respond($method, $route[0], function() use($route) {

			// Create new Controller object
			$controller = '\App\Controllers\\' . $route[1];
			$controller = new $controller(self::$router);

			// Complete action variable
			if ($route[2] == '') {
				$route[2] = 'indexAction';
			}
			else {
				$route[2] .= 'Action';
			}

			$stillValid = true;

			// If method does not exist in object
			if (!method_exists($controller, $route[2])) {
				$stillValid = false;
			}

			// If no valid permissions
			if ($controller->getAdminSection() &&
				$controller->getLoggedIn() == false) {
				$stillValid = false;
			}

			// Call Controller action
			if ($stillValid) {
				if (is_array($route[3])) {
					return $controller->{$route[2]}(
						$route[3][0] ?? '',
						$route[3][1] ?? '',
						$route[3][2] ?? ''
					);
				}
				else if ($route[3] != '') {
					return $controller->{$route[2]}($route[3]);
				}
				else {
					return $controller->{$route[2]}();
				}
			}
			else {
				$controller->throw404();
			}
		});
	}

	public static function createNavigation(): void
	{
		// Pull from cache
		$sections = Db::getSections();
		$pages = Db::getPages();

		// [
		// 	[
		// 		'section url',
		// 		'title',
		// 		['page url', 'title'],
		// 		['page url', 'titleOfPage2'],
		// 	],
		// 	[],
		// 	[],
		// ]

		$navigation = [];

		// Generate sections
		foreach ($sections as $section) {
			// Skip hidden sections
			if ($section['hide_navigation'] == '1') {
				continue;
			}

			// Add URL, title to ID
			$navigation[$section['id']] = [
				$section['section'], $section['title']
			];
		}

		// Generate pages
		foreach ($pages as $page) {
			// Skip hidden sections
			if (!_exists($navigation, $page['section_id'])) {
				continue;
			}

			// Skip hidden pages
			if ($page['hide_navigation'] == '1') {
				continue;
			}

			// Add [URL, title] to ID
			$navigation[$page['section_id']][] = [
				$page['page'], $page['title']
			];
		}

		self::$router->service()->navigation = $navigation;
	}

	//-------------------------------------//

	protected static function setHttpError(): void
	{
		self::$router->onHttpError(function($code) {
			$service = self::$router->service();

			switch ($code) {
				case 404:

					// @Todo: find out why this was needed and remove if possible
					$service->escape = function (?string $string) {
						return htmlentities($string, ENT_QUOTES | ENT_HTML5, 'UTF-8');
					};

					self::$router->response()->sendHeaders(true, true);
					$service->pageTitle = 'Error 404 (Not Found)';
					$service->render('../app/views/errors/404.php');
					break;
			}
		});
	}

	//-------------------------------------//

	public static function getRoutes(): array
	{
		return self::$routes;
	}

}

Router::_init();

// @Todo
// - combine addRoute and addBasicroute functionality