<?php

namespace MyAAC;

use Composer\Semver\Semver;
use MyAAC\Cache\Cache;
use MyAAC\Models\Menu;

class Plugins {
	private static array $warnings = [];
	private static string $error = '';
	private static array $plugin_json = [];

	const DEFAULT_PRIORITY = 1000;

	public static function getInits()
	{
		return Cache::remember('plugins_inits', 10 * 60, function () {
			$inits = [];
			foreach(self::getAllPluginsJson() as $plugin) {
				if (!self::getAutoLoadOption($plugin, 'init', false)) {
					continue;
				}

				$initPriority = self::DEFAULT_PRIORITY;
				if (isset($plugin['autoload']['init-priority'])) {
					$initPriority = (int) $plugin['autoload']['init-priority'];
				}

				$pluginInits = glob(PLUGINS . $plugin['filename'] . '/init.php');
				foreach ($pluginInits as $path) {
					$inits[] = [
						'file' => $path,
						'priority' => $initPriority
					];
				}
			}

			usort($inits, function ($a, $b)
			{
				return $a['priority'] <=> $b['priority'];
			});

			$ret = [];
			foreach ($inits as $init) {
				$ret[] = $init['file'];
			}

			return $ret;
		});
	}

	public static function getAdminPages()
	{
		return Cache::remember('plugins_admin_pages', 10 * 60, function () {
			$adminPages = [];
			foreach(self::getAllPluginsJson() as $plugin) {
				if (!self::getAutoLoadOption($plugin, 'admin-pages', true)) {
					continue;
				}

				$adminPagesDefaultPriority = self::DEFAULT_PRIORITY;
				if (isset($plugin['admin-pages-default-priority'])) {
					$adminPagesDefaultPriority = $plugin['admin-pages-default-priority'];
				}

				//
				// Get all plugins/*/admin-pages/*.php pages
				//
				$pluginAdminPages = glob(PLUGINS . $plugin['filename'] . '/admin-pages/*.php');
				foreach ($pluginAdminPages as $file) {
					$file = str_replace(PLUGINS, 'plugins/', $file);
					$name = pathinfo($file, PATHINFO_FILENAME);

					$adminPages[] = ['name' => $name, 'file' => $file, 'priority' => $adminPagesDefaultPriority];
				}

				if (self::getAutoLoadOption($plugin, 'admin-pages-sub-folders', true)) {
					//
					// Get all plugins/*/admin-pages/subFolder/*.php pages
					//
					$pluginAdminPagesSubFolders = glob(PLUGINS . $plugin['filename'] . '/admin-pages/*', GLOB_ONLYDIR);
					foreach ($pluginAdminPagesSubFolders as $folder) {
						$folderName = pathinfo($folder, PATHINFO_FILENAME);

						$subFiles = glob(PLUGINS . $plugin['filename'] . '/admin-pages/' . $folderName . '/*.php');
						foreach ($subFiles as $file) {
							$file = str_replace(PLUGINS, 'plugins/', $file);
							$name = $folderName . '/' . pathinfo($file, PATHINFO_FILENAME);

							$adminPages[] = ['name' => $name, 'file' => $file, 'priority' => $adminPagesDefaultPriority];
						}

						$subFolders = glob(PLUGINS . $plugin['filename'] . '/admin-pages/' . $folderName . '/*', GLOB_ONLYDIR);
						foreach ($subFolders as $subFolder) {
							$subFolderName = pathinfo($subFolder, PATHINFO_FILENAME);
							$subSubFiles = glob(PLUGINS . $plugin['filename'] . '/admin-pages/' . $folderName . '/' . $subFolderName . '/*.php');

							foreach ($subSubFiles as $subSubFile) {
								$subSubFile = str_replace(PLUGINS, 'plugins/', $subSubFile);
								$name = $folderName . '/' . $subFolderName . '/' . pathinfo($subSubFile, PATHINFO_FILENAME);

								$adminPages[] =  ['name' => $name, 'file' => $subSubFile, 'priority' => $adminPagesDefaultPriority];;
							}
						}
					}
				}
			}

			usort($adminPages, function ($a, $b)
			{
				if ($a['priority'] == $b['priority']) {
					return 0;
				}

				return ($a['priority'] > $b['priority']) ? -1 : 1;
			});

			$ret = [];
			foreach ($adminPages as $value) {
				$ret[$value['name']] = $value['file'];
			}

			return $ret;
		});
	}

	public static function getRoutes()
	{
		$cache = Cache::getInstance();
		if ($cache->enabled()) {
			$tmp = '';
			if ($cache->fetch('plugins_routes', $tmp)) {
				return unserialize($tmp);
			}
		}

		$routes = [];
		foreach(self::getAllPluginsJson() as $plugin) {
			$routesDefaultPriority = self::DEFAULT_PRIORITY;
			if (isset($plugin['routes-default-priority'])) {
				$routesDefaultPriority = $plugin['routes-default-priority'];
			}

			$warningPreTitle = 'Plugin: ' . $plugin['name'] . ' - ';

			if (isset($plugin['routes'])) {
				foreach ($plugin['routes'] as $info) {
					// default method: get
					$method = $info['method'] ?? ['GET'];
					if ($method !== '*') {
						$methods = is_string($method) ? explode(',', $info['method']) : $method;
						foreach ($methods as $method) {
							$method = strtolower($method);
							if (!in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'head'])) {
								self::$warnings[] = $warningPreTitle . 'Not allowed method ' . $method . '... Disabling this route...';
							}
						}
					}
					else {
						$methods = '*'; // all available methods
					}

					if (!isset($info['priority'])) {
						$info['priority'] = $routesDefaultPriority; // default priority taken from plugin.json
					}

					if (isset($info['redirect_from'])) {
						removeIfFirstSlash($info['redirect_from']);

						$info['pattern'] = $info['redirect_from'];
						if (!isset($info['redirect_to'])) {
							self::$warnings[] = $warningPreTitle . 'redirect set without "redirect_to".';
						}
						else {
							removeIfFirstSlash($info['redirect_to']);
							$info['file'] = '__redirect__/' . $info['redirect_to'];
						}
					}

					// replace first occurrence of / in pattern if found (will be auto-added later)
					removeIfFirstSlash($info['pattern']);

					$routes[] = [$methods, $info['pattern'], $info['file'], $info['priority']];
				}
			}

			$pagesDefaultPriority = self::DEFAULT_PRIORITY;
			if (isset($plugin['pages-default-priority'])) {
				$pagesDefaultPriority = $plugin['pages-default-priority'];
			}

			$autoLoadPages = self::getAutoLoadOption($plugin, 'pages', true);
			if ($autoLoadPages) {
				//
				// Get all plugins/*/pages/*.php pages
				//
				$pluginPages = glob(PLUGINS . $plugin['filename'] . '/pages/*.php');
				foreach ($pluginPages as $file) {
					$file = str_replace(PLUGINS, 'plugins/', $file);
					$name = pathinfo($file, PATHINFO_FILENAME);

					$routes[] = [['get', 'post'], $name, $file, $pagesDefaultPriority];
				}
			}

			if ($autoLoadPages && self::getAutoLoadOption($plugin, 'pagesSubFolders', true) &&
				self::getAutoLoadOption($plugin, 'pages-sub-folders', true)) {
				//
				// Get all plugins/*/pages/subFolder/*.php pages
				//
				$pluginPagesSubFolders = glob(PLUGINS . $plugin['filename'] . '/pages/*', GLOB_ONLYDIR);
				foreach ($pluginPagesSubFolders as $folder) {
					$folderName = pathinfo($folder, PATHINFO_FILENAME);

					$subFiles = glob(PLUGINS . $plugin['filename'] . '/pages/' . $folderName . '/*.php');
					foreach ($subFiles as $file) {
						$file = str_replace(PLUGINS, 'plugins/', $file);
						$name = $folderName . '/' . pathinfo($file, PATHINFO_FILENAME);

						$routes[] = [['get', 'post'], $name, $file, $pagesDefaultPriority];
					}

					$subFolders = glob(PLUGINS . $plugin['filename'] . '/pages/' . $folderName . '/*', GLOB_ONLYDIR);
					foreach ($subFolders as $subFolder) {
						$subFolderName = pathinfo($subFolder, PATHINFO_FILENAME);
						$subSubFiles = glob(PLUGINS . $plugin['filename'] . '/pages/' . $folderName . '/' . $subFolderName . '/*.php');

						foreach ($subSubFiles as $subSubFile) {
							$subSubFile = str_replace(PLUGINS, 'plugins/', $subSubFile);
							$name = $folderName . '/' . $subFolderName . '/' . pathinfo($subSubFile, PATHINFO_FILENAME);

							$routes[] = [['get', 'post'], $name, $subSubFile, $pagesDefaultPriority];
						}
					}
				}
			}
		}

		global $hooks;
		$hooks->triggerFilter(HOOK_FILTER_ROUTES, $routes);

		usort($routes, function ($a, $b)
		{
			// key 3 is priority
			if ($a[3] == $b[3]) {
				return 0;
			}

			return ($a[3] < $b[3]) ? -1 : 1;
		});

		// cleanup before passing back
		// priority is not needed anymore
		//foreach ($routes as &$route) {
		//	unset($route[3]);
		//}

		if ($cache->enabled()) {
			$cache->set('plugins_routes', serialize($routes), 600);
		}

		return $routes;
	}

	public static function getThemes()
	{
		$cache = Cache::getInstance();
		if ($cache->enabled()) {
			$tmp = '';
			if ($cache->fetch('plugins_themes', $tmp)) {
				return unserialize($tmp);
			}
		}

		$themes = [];
		foreach(self::getAllPluginsJson() as $plugin) {
			if (!self::getAutoLoadOption($plugin, 'themes', true)) {
				continue;
			}

			$pluginThemes = glob(PLUGINS . $plugin['filename'] . '/themes/*', GLOB_ONLYDIR);
			foreach ($pluginThemes as $path) {
				$path = str_replace(PLUGINS, 'plugins/', $path);
				$name = pathinfo($path, PATHINFO_FILENAME);

				$themes[$name] = $path;
			}
		}

		if ($cache->enabled()) {
			$cache->set('plugins_themes', serialize($themes), 600);
		}

		return $themes;
	}

	public static function getCommands()
	{
		$cache = Cache::getInstance();
		if ($cache->enabled()) {
			$tmp = '';
			if ($cache->fetch('plugins_commands', $tmp)) {
				return unserialize($tmp);
			}
		}

		$commands = [];
		foreach(self::getAllPluginsJson() as $plugin) {
			if (!self::getAutoLoadOption($plugin, 'commands', true)) {
				continue;
			}

			$pluginCommands = glob(PLUGINS . $plugin['filename'] . '/commands/*.php');
			foreach ($pluginCommands as $path) {
				$commands[] = $path;
			}
		}

		if ($cache->enabled()) {
			$cache->set('plugins_commands', serialize($commands), 600);
		}

		return $commands;
	}

	public static function getHooks()
	{
		$cache = Cache::getInstance();
		if ($cache->enabled()) {
			$tmp = '';
			if ($cache->fetch('plugins_hooks', $tmp)) {
				return unserialize($tmp);
			}
		}

		$hooks = [];
		foreach(self::getAllPluginsJson() as $plugin) {
			if (isset($plugin['hooks'])) {
				foreach ($plugin['hooks'] as $_name => $info) {
					$priority = self::DEFAULT_PRIORITY;

					if (str_contains($info['type'], 'HOOK_')) {
						$info['type'] = str_replace('HOOK_', '', $info['type']);
					}

					if (isset($info['priority'])) {
						$priority = (int)$info['priority'];
					}

					if (defined('HOOK_'. $info['type'])) {
						$hook = constant('HOOK_'. $info['type']);
						$hooks[] = ['name' => $_name, 'type' => $hook, 'file' => $info['file'], 'priority' => $priority];
					} else {
						self::$warnings[] = 'Plugin: ' . $plugin['name'] . '. Unknown event type: ' . $info['type'];
					}
				}
			}
		}

		usort($hooks, function ($a, $b)
		{
			if ($a['priority'] == $b['priority']) {
				return 0;
			}

			return ($a['priority'] < $b['priority']) ? -1 : 1;
		});

		if ($cache->enabled()) {
			$cache->set('plugins_hooks', serialize($hooks), 600);
		}

		return $hooks;
	}

	public static function getAllPluginsSettings()
	{
		$cache = Cache::getInstance();
		if ($cache->enabled()) {
			$tmp = '';
			if ($cache->fetch('plugins_settings', $tmp)) {
				return unserialize($tmp);
			}
		}

		$settings = [];
		foreach (self::getAllPluginsJson() as $plugin) {
			if (!self::getAutoLoadOption($plugin, 'settings', true)) {
				continue;
			}

			$settingsFileName = PLUGINS . $plugin['filename'] . '/settings.php';
			if (!is_file($settingsFileName)) {
				continue;
			}

			$settingsFile = require $settingsFileName;
			if (!isset($settingsFile['key'])) {
				warning("Settings file for plugin - {$plugin['name']} does not contain 'key' field");
				continue;
			}

			$settings[$settingsFile['key']] = ['pluginFilename' => $plugin['filename'], 'settingsFilename' => 'plugins/' . $plugin['filename'] . '/settings.php'];
		}

		foreach (self::getAllPluginsJson() as $plugin) {
			if (isset($plugin['settings'])) {
				$settingsFile = require BASE . $plugin['settings'];
				if (!isset($settingsFile['key'])) {
					warning("Settings file for plugin - {$plugin['name']} does not contain 'key' field");
					continue;
				}

				$settings[$settingsFile['key']] = ['pluginFilename' => $plugin['filename'], 'settingsFilename' => $plugin['settings']];
			}
		}

		if ($cache->enabled()) {
			$cache->set('plugins_settings', serialize($settings), 600); // cache for 10 minutes
		}

		return $settings;
	}

	public static function getAllPluginsJson($disabled = false)
	{
		$cache = Cache::getInstance();
		if ($cache->enabled()) {
			$tmp = '';
			if ($cache->fetch('plugins', $tmp)) {
				return unserialize($tmp);
			}
		}

		$plugins = [];
		foreach (get_plugins($disabled) as $filename) {
			$plugin = self::getPluginJson($filename);

			if (!$plugin) {
				continue;
			}

			$plugin['filename'] = $filename;
			$plugins[] = $plugin;
		}

		if ($cache->enabled()) {
			$cache->set('plugins', serialize($plugins), 600); // cache for 10 minutes
		}

		return $plugins;
	}

	public static function getPluginSettings($filename): mixed
	{
		$plugin_json = self::getPluginJson($filename);
		if (!$plugin_json) {
			return false;
		}

		$settingsFileName = PLUGINS . $plugin_json['filename'] . '/settings.php';
		if (!is_file($settingsFileName)) {
			if (!isset($plugin_json['settings']) || !is_file(BASE . $plugin_json['settings'])) {
				return false;
			}
		}
		else {
			return 'plugins/' . $plugin_json['filename'] . '/settings.php';
		}

		return $plugin_json['settings'];
	}

	public static function getPluginJson($filename = null)
	{
		if(!isset($filename)) {
			return self::$plugin_json;
		}

		$pathToPlugin = PLUGINS . $filename . '.json';
		if (!file_exists($pathToPlugin)) {
			self::$warnings[] = "Cannot load $filename.json. File doesn't exist.";
			return false;
		}

		$string = file_get_contents($pathToPlugin);
		$plugin_json = json_decode($string, true);
		if ($plugin_json == null) {
			self::$warnings[] = "Cannot load $filename.json. File might be not a valid json code.";
			return false;
		}

		if (isset($plugin_json['enabled']) && !getBoolean($plugin_json['enabled'])) {
			self::$warnings[] = 'Skipping ' . $filename . '... The plugin is disabled.';
			return false;
		}

		$plugin_json['filename'] = $filename;

		return $plugin_json;
	}

	public static function install($file): bool
	{
		global $db;

		if(!\class_exists('\ZipArchive')) {
			throw new \RuntimeException('Please install PHP zip extension. Plugins upload disabled until then.');
		}

		$zip = new \ZipArchive();
		if($zip->open($file) !== true) {
			self::$error = 'There was a problem with opening zip archive.';
			return false;
		}

		for ($i = 0; $i < $zip->numFiles; $i++) {
			$tmp = $zip->getNameIndex($i);
			if(pathinfo($tmp, PATHINFO_DIRNAME) == 'plugins' && pathinfo($tmp, PATHINFO_EXTENSION) == 'json')
				$json_file = $tmp;
		}

		if(!isset($json_file)) {
			self::$error = 'Cannot find plugin info .json file. Installation is discontinued.';
			return false;
		}

		$plugin_temp_dir = CACHE . 'plugins/' . str_replace('.zip', '', basename($file)) . '/';
		if(!$zip->extractTo($plugin_temp_dir)) { // place in cache dir
			self::$error = 'There was a problem with extracting zip archive to cache directory.';
			$zip->close();
			return false;
		}

		ensureFolderExists($cachePlugins = CACHE . 'plugins');
		ensureIndexExists($cachePlugins);

		self::$error = 'There was a problem with extracting zip archive.';
		$file_name = $plugin_temp_dir . $json_file;
		if(!file_exists($file_name)) {
			self::$error = "Cannot load " . $file_name . ". File doesn't exist.";
			$zip->close();
			return false;
		}

		$pluginFilename = str_replace('.json', '', basename($json_file));
		if (self::existDisabled($pluginFilename)) {
			success('The plugin already existed, but was disabled. It has been enabled again and will be now reinstalled.');
			self::enable($pluginFilename);
		}

		$string = file_get_contents($file_name);
		$plugin_json = json_decode($string, true);
		self::$plugin_json = $plugin_json;
		if ($plugin_json == null) {
			self::$warnings[] = 'Cannot load ' . $file_name . '. File might be not a valid json code.';
			return false;
		}

		$continue = true;

		if(!isset($plugin_json['name']) || empty(trim($plugin_json['name']))) {
			self::$error = 'Plugin "name" tag is not set.';
			return false;
		}

		if(!isset($plugin_json['version']) || empty(trim($plugin_json['version']))) {
			self::$warnings[] = 'Plugin "version" tag is not set.';
		}

		if(isset($plugin_json['require'])) {
			$require = $plugin_json['require'];

			$myaac_satisfied = true;
			if(isset($require['myaac_'])) {
				$require_myaac = $require['myaac_'];
				if(!Semver::satisfies(MYAAC_VERSION, $require_myaac)) {
					$myaac_satisfied = false;
				}
			}
			else if(isset($require['myaac'])) {
				$require_myaac = $require['myaac'];
				if(version_compare(MYAAC_VERSION, $require_myaac, '<')) {
					$myaac_satisfied = false;
				}
			}

			if(!$myaac_satisfied) {
				self::$error = "Your AAC version doesn't meet the requirement of this plugin. Required version is: " . $require_myaac . ", and you're using version " . MYAAC_VERSION . ".";
				return false;
			}

			$php_satisfied = true;
			if(isset($require['php_'])) {
				$require_php = $require['php_'];
				if(!Semver::satisfies(phpversion(), $require_php)) {
					$php_satisfied = false;
				}
			}
			else if(isset($require['php'])) {
				$require_php = $require['php'];
				if(version_compare(phpversion(), $require_php, '<')) {
					$php_satisfied = false;
				}
			}

			if(!$php_satisfied) {
				self::$error = "Your PHP version doesn't meet the requirement of this plugin. Required version is: " . $require_php . ", and you're using version " . phpversion() . ".";
				$continue = false;
			}

			$database_satisfied = true;
			if(isset($require['database_'])) {
				$require_database = $require['database_'];
				if(!Semver::satisfies(DATABASE_VERSION, $require_database)) {
					$database_satisfied = false;
				}
			}
			else if(isset($require['database'])) {
				$require_database = $require['database'];
				if(version_compare(DATABASE_VERSION, $require_database, '<')) {
					$database_satisfied = false;
				}
			}

			if(!$database_satisfied) {
				self::$error = "Your database version doesn't meet the requirement of this plugin. Required version is: " . $require_database . ", and you're using version " . DATABASE_VERSION . ".";
				$continue = false;
			}

			if($continue) {
				foreach($require as $req => $version) {
					$req = strtolower(trim($req));
					$version = trim($version);

					if(in_array($req, array('myaac', 'myaac_', 'php', 'php_', 'database', 'database_'))) {
						continue;
					}

					if(in_array($req, array('php-ext', 'php-extension'))) { // require php extension
						$tmpDisplayError = false;
						$explode = explode(',', $version);

						foreach ($explode as $item) {
							if(!extension_loaded($item)) {
								$errors[] = "This plugin requires php extension: " . $item . " to be installed.";
								$tmpDisplayError = true;
							}
						}

						if ($tmpDisplayError) {
							self::$error = implode('<br/>', $errors);
							$continue = false;
							break;
						}
					}
					else if($req == 'table') {
						$tmpDisplayError = false;
						$explode = explode(',', $version);
						foreach ($explode as $item) {
							if(!$db->hasTable($item)) {
								$errors[] = "This plugin requires table: " . $item . " to exist in the database.";
								$tmpDisplayError = true;
							}
						}

						if ($tmpDisplayError) {
							self::$error = implode('<br/>', $errors);
							$continue = false;
							break;
						}
					}
					else if($req == 'column') {
						$tmpDisplayError = false;
						$explode = explode(',', $version);
						foreach ($explode as $item) {
							$tmp = explode('.', $item);

							if(count($tmp) == 2) {
								if(!$db->hasColumn($tmp[0], $tmp[1])) {
									$errors[] = "This plugin requires database column: " . $tmp[0] . "." . $tmp[1] . " to exist in database.";
									$tmpDisplayError = true;
								}
							}
							else {
								self::$warnings[] = "Invalid plugin require column: " . $item;
							}
						}

						if ($tmpDisplayError) {
							self::$error = implode('<br/>', $errors);
							$continue = false;
							break;
						}
					}
					else if(strpos($req, 'ext-') !== false) {
						$tmp = explode('-', $req);
						if(count($tmp) == 2) {
							if(!extension_loaded($tmp[1]) || !Semver::satisfies(phpversion($tmp[1]), $version)) {
								self::$error = "This plugin requires php extension: " . $tmp[1] . ", version " . $version . " to be installed.";
								$continue = false;
								break;
							}
						}
					}
					else if(!self::is_installed($req, $version)) {
						self::$error = "This plugin requires another plugin to run correctly. The another plugin is: " . $req . ", with version " . $version . ".";
						$continue = false;
						break;
					}
				}
			}
		}

		if(!$continue) {
			return false;
		}

		if(!$zip->extractTo(BASE)) { // "Real" Install
			self::$error = 'There was a problem with extracting zip archive to base directory.';
			$zip->close();
			return false;
		}

		$install = $plugin_json['install'] ?? '';
		if (self::getAutoLoadOption($plugin_json, 'install', true) && is_file(PLUGINS . $pluginFilename . '/install.php')) {
			$install = 'plugins/' . $pluginFilename . '/install.php';
		}

		if (!empty($install)) {
			if (file_exists(BASE . $install)) {
				$db->revalidateCache();
				require BASE . $install;
				$db->revalidateCache();
			}
			else {
				self::$warnings[] = 'Cannot load install script. Your plugin might be not working correctly.';
			}
		}

		clearCache();
		return true;
	}

	public static function isEnabled($pluginFileName): bool
	{
		$filenameJson = $pluginFileName . '.json';
		return !is_file(PLUGINS . 'disabled.' . $filenameJson) && is_file(PLUGINS . $filenameJson);
	}

	public static function existDisabled($pluginFileName): bool
	{
		$filenameJson = $pluginFileName . '.json';
		return is_file(PLUGINS . 'disabled.' . $filenameJson);
	}

	public static function enable($pluginFileName): bool {
		return self::enableDisable($pluginFileName, true);
	}

	public static function disable($pluginFileName): bool {
		return self::enableDisable($pluginFileName, false);
	}

	private static function enableDisable($pluginFileName, $enable): bool
	{
		$filenameJson = $pluginFileName . '.json';
		$fileExist = is_file(PLUGINS . ($enable ? 'disabled.' : '') . $filenameJson);
		if (!$fileExist) {
			self::$error = 'Cannot ' . ($enable ? 'enable' : 'disable') . ' plugin: ' . $pluginFileName . '. File does not exist.';
			return false;
		}

		$result = rename(PLUGINS . ($enable ? 'disabled.' : '') . $filenameJson, PLUGINS . ($enable ? '' : 'disabled.') . $filenameJson);
		if (!$result) {
			self::$error = 'Cannot ' . ($enable ? 'enable' : 'disable') . ' plugin: ' . $pluginFileName . '. Permission problem.';
			return false;
		}

		clearCache();
		return true;
	}

	/**
	 * This function is to execute the "install" part of the plugin
	 *
	 * @param $plugin_name
	 * @return bool
	 */
	public static function executeInstall($plugin_name): bool
	{
		$filename = BASE . 'plugins/' . $plugin_name . '.json';
		if(!file_exists($filename)) {
			self::$error = 'Plugin ' . $plugin_name . ' does not exist.';
			return false;
		}

		$string = file_get_contents($filename);
		$plugin_json = json_decode($string, true);
		if(!$plugin_json) {
			self::$error = 'Cannot load plugin info ' . $plugin_name . '.json';
			return false;
		}

		$install = $plugin_json['install'] ?? '';
		if (self::getAutoLoadOption($plugin_json, 'install', true) && is_file(PLUGINS . $plugin_name . '/install.php')) {
			$install = 'plugins/' . $plugin_name . '/install.php';
		}

		if (empty($install)) {
			self::$error = "This plugin doesn't seem to have install script defined.";
			return false;
		}

		global $db;
		if (file_exists(BASE . $install)) {
			$db->revalidateCache();
			require BASE . $install;
			$db->revalidateCache();
		}
		else {
			self::$warnings[] = 'Cannot load install script. Your plugin might be not working correctly.';
		}

		return true;
	}

	public static function uninstall($plugin_name): bool
	{
		$isDisabled = self::existDisabled($plugin_name);
		if($isDisabled) {
			self::enable($plugin_name);
		}

		$revertEnable = function() use ($isDisabled, $plugin_name) {
			if($isDisabled) {
				self::disable($plugin_name);
			}
		};

		$filename = BASE . 'plugins/' . $plugin_name . '.json';
		if(!file_exists($filename)) {
			self::$error = 'Plugin ' . $plugin_name . ' does not exist.';
			$revertEnable();
			return false;
		}

		$string = file_get_contents($filename);
		$plugin_info = json_decode($string, true);
		if(!$plugin_info) {
			self::$error = 'Cannot load plugin info ' . $plugin_name . '.json';
			$revertEnable();
			return false;
		}

		if(!isset($plugin_info['uninstall'])) {
			self::$error = "Plugin doesn't have uninstall options defined. Skipping...";
			$revertEnable();
			return false;
		}

		$success = true;
		foreach($plugin_info['uninstall'] as $file) {
			if(str_starts_with($file, '/')) {
				$success = false;
				self::$error = "You cannot use absolute paths (starting with slash - '/'): " . $file;
				break;
			}

			$file = str_replace('\\', '/', BASE . $file);
			$realpath = str_replace('\\', '/', realpath(dirname($file)));
			if(!is_sub_dir($file, BASE) || $realpath != dirname($file)) {
				$success = false;
				self::$error = "You don't have rights to delete: " . $file;
				break;
			}
		}

		if(!$success) {
			$revertEnable();
			return false;
		}

		foreach($plugin_info['uninstall'] as $file) {
			if(!deleteDirectory(BASE . $file)) {
				self::$warnings[] = 'Cannot delete: ' . $file;
			}
		}

		global $hooks;
		foreach($plugin_info['hooks'] ?? [] as $name => $info) {
			if (str_contains($info['type'], 'HOOK_')) {
				$info['type'] = str_replace('HOOK_', '', $info['type']);
			}

			$hooks->unregister($name, 'HOOK_' . $info['type'], $info['file']);
		}

		clearCache();
		return true;
	}

	public static function is_installed($plugin_name, $version): bool
	{
		$filename = BASE . 'plugins/' . $plugin_name . '.json';
		if(!file_exists($filename)) {
			return false;
		}

		$string = file_get_contents($filename);
		$plugin_info = json_decode($string, true);
		if(!$plugin_info) {
			return false;
		}

		if(!isset($plugin_info['version'])) {
			return false;
		}

		return Semver::satisfies($plugin_info['version'], $version);
	}

	public static function getWarnings(): array {
		return self::$warnings;
	}

	public static function clearWarnings(): void {
		self::$warnings = [];
	}

	public static function getError(): string {
		return self::$error;
	}

	/**
	 * Install menus
	 * Helper function for plugins
	 *
	 * @param string $templateName
	 * @param array $menus
	 */
	public static function installMenus($templateName, $menus, $clearOld = false): void
	{
		global $db;

		if ($clearOld) {
			Menu::where('template', $templateName)->delete();
		}

		if (Menu::where('template', $templateName)->count()) {
			return;
		}

		foreach ($menus as $category => $_menus) {
			$i = 0;
			foreach ($_menus as $name => $link) {
				$color = '';
				$blank = 0;

				if (is_array($link)) {
					if (isset($link['name'])) {
						$name = $link['name'];
					}
					if (isset($link['color'])) {
						$color = $link['color'];
					}
					if (isset($link['blank'])) {
						$blank = $link['blank'] ? 1 : 0;
					}

					$link = $link['link'];
				}

				$insert_array = [
					'template' => $templateName,
					'name' => $name,
					'link' => $link,
					'category' => $category,
					'ordering' => $i++,
				];

				// support for color and blank attributes
				if($db->hasColumn(TABLE_PREFIX . 'menu', 'blank') && $db->hasColumn(TABLE_PREFIX . 'menu', 'color')) {
					$insert_array['blank'] = $blank;
					$insert_array['color'] = $color;
				}

				Menu::create($insert_array);
			}
		}
	}

	private static function getAutoLoadOption(array $plugin, string $optionName, bool $default = true): bool
	{
		if (isset($plugin['autoload'])) {
			$autoload = $plugin['autoload'];
			if (is_array($autoload)) {
				if (isset($autoload[$optionName])) {
					return getBoolean($autoload[$optionName]);
				}
			}
			elseif (is_bool($autoload)) {
				return $autoload;
			}
		}

		return $default;
	}
}
