<?php

	namespace VLW\Helpers;

	use \VV;

	use VLW\Database\Database;
	use VLW\Database\Models\Languages\Language;
	use VLW\Database\Tables\Languages\Languages;

	require_once VV::root("src/Database/Database.php");
	require_once VV::root("src/Database/Models/Languages/Language.php");
	require_once VV::root("src/Database/Tables/Languages/Languages.php");

	class Forgejo {
		private const MERGE_LANGUAGES = [
			"Hack" => "PHP"
		];
		private const FORGEJO_ENDPOINT_USER   = "/api/v1/users/%s";
		private const FORGEJO_ENDPOINT_SEARCH = "/api/v1/repos/search?uid=%s";

		private array $languages;
		private readonly Database $db;

		// Fetch JSON from URL
		private static function fetch_json(string $url): array {
			return json_decode(file_get_contents($url), true);
		}

		// Fetch JSON from a Forgejo endpoint
		private static function fetch_endpoint(string $endpoint): array {
			$url = $_ENV["service_forgejo"]["url"] . $endpoint;
			return self::fetch_json($url);
		}

		public function __construct() {
			$this->db = new Database();
			$this->languages = [];
		}

		// Add languages from all public repositories for profiles in config
		public function update(): bool {
			foreach(explode(",", $_ENV["service_forgejo"]["profiles"]) as $profile) {
				// Resolve user data from username
				$user = self::fetch_endpoint(sprintf(self::FORGEJO_ENDPOINT_USER, $profile));
				if (!$this->add_public_repositores($user["id"])) {
					return false;
				}
			}

			$this->save_languages();
			return true;
		}

		private function truncate(): bool {
			return $this->db->from(Languages::TABLE)->delete();
		}

		// Save languages from $this->languages to the database
		private function save_languages(): void {
			$this->truncate();

			foreach ($this->languages as $language => $bytes) {
				Language::new($language, $bytes);
			}
		}

		// Fetch and add languages to total from a fully-qualified Forgejo URL
		private function add_repository_languages(string $url): void {
			foreach(self::fetch_json($url) as $language => $bytes) {
				// Create key for language if it doesn't exist
				if (!array_key_exists($language, $this->languages)) {
					$this->languages[$language] = 0;
				}

				// Merge language with another if exists in merge array
				if (array_key_exists($language, self::MERGE_LANGUAGES)) {
					$language = self::MERGE_LANGUAGES[$language];
				}

				// Add bytes to language in total
				$this->languages[$language] += $bytes;
			}
		}

		// Tally languages from public repositories for user id
		private function add_public_repositores(int $uid): bool {
			$resp = self::fetch_endpoint(sprintf(self::FORGEJO_ENDPOINT_SEARCH, $uid));

			// Bail out if request failed or if response indicated a problem
			if (!$resp or $resp["ok"] === false) {
				return false;
			}

			// Add langauges for each public repository
			foreach ($resp["data"] as $repo) {
				$this->add_repository_languages($repo["languages_url"]);
			}

			return true;
		}
	}
