Source: utils.js

const chalk = require('chalk');
const axios = require('axios');
const clipboard = require('copy-paste')

const chart = require('@wangnene2/chart')
const { exec } = require('node:child_process');
const { Toggle, Confirm, prompt, AutoComplete, Survey, Input } = require('enquirer');


const init = require('../utils/init');
const constants = require('./constants');

const { bar, bg, annotation } = chart;
const Parser = require('expr-eval').Parser;
const parser = new Parser();

const { MAID_NAME, getRandomMaidEmoji, appendQuotes, APIDICT, CONSTANTS, get_random, formatObjectFeatures, countDecimals } = constants;
const { getMaidDirectory } = require('./utils_functions');
const DSATrainer = require('./dsa-cli/dsa-trainer');

const Settings = require('./settings.js');
const DEV_MODE = Settings.dev_mode ?? false;

const { Quizzer: FlashQuizzer } = require(
	"./Quizzer"
);

// https://www.npmjs.com/package/chalk

class DayWeather {
	constructor(jsonDay) {
		const SNOW = "snow";
		const RAIN = "rain";

		this.datetime = jsonDay?.datetime;
		this.description = jsonDay?.description;
		this.isPrecipitation = jsonDay.preciptype ? true : false;

		this.hasSnow = this.isPrecipitation ? jsonDay.preciptype.includes(SNOW) : false;
		this.hasRain = this.isPrecipitation ? jsonDay.preciptype.includes(RAIN) : false;
		this.probability = jsonDay.precipprob ? jsonDay.precipprob : 0;
		this.day = this.datetime.slice(-2);
	}
}

const COLORWEATHERMAP = {
	snow: 'white',
	rain: 'blue',
	clear: 'yellow'
}

class WeatherInformation {
	// A wrapper for weather information. that populates itself


	constructor(jsonData) {
		this.json = jsonData;
		this.days_report = jsonData.data.days.map(
			dayJSON => {
				return new DayWeather(dayJSON)
			}
		)

		this.barData = this.days_report.slice(0, 7).map(dWeather => {
			let barColor = dWeather.isPrecipitation ? dWeather.hasSnow ? COLORWEATHERMAP.snow : COLORWEATHERMAP.rain : COLORWEATHERMAP.clear;

			const bar = { key: dWeather.day, value: dWeather.probability, style: bg(barColor) };
			return bar;

		})
		// console.log(bar(barData))
	}

	chartWeatherBar() {
		console.log(bar(this.barData));
		this.printWeatherAnnotations()
	}

	printWeatherAnnotations() {
		const notes = Object.keys(COLORWEATHERMAP).map((weatherlabel) => {
			return { key: weatherlabel, style: bg(COLORWEATHERMAP[weatherlabel]) };
		})
		console.log(annotation(notes))
	}
}


/**
 * Structure for Bar Charting
 */
class FeatureExtraction {

	constructor(feature_name, feature_key = 'feat', style = bg('white'), getDayOnly = true) {
		this.feature_name = feature_name;
		this.feature_key = feature_key;
		this.style = style;
	}
};


const { getRandomProblem, copyFileToTemp } = require('./data-science-cli/index');
const { get } = require('node:http');
const { strict } = require('node:assert');

class Maid {

	constructor(name = MAID_NAME, headerColor = '#1da1f2', clearOnTalk = false) {
		this.name = name;
		this.headerColor = headerColor;
		this.clearOnTalk = clearOnTalk;
		this.missing_features_today = []; //To be populated when required.
	}

	getMaidHeader = () => {
		return `${chalk.hex(this.headerColor).inverse(` ${this.name}: `)}`;
	};

	say(message, clearOnTalk = this.clearOnTalk) {

		if (clearOnTalk) init(true);
		console.log(`${this.getMaidHeader()} ${chalk(message)}`);
	}

	tellCurrentDirectory = () => {
		const projectDirectory = getMaidDirectory();
		this.say(projectDirectory);
		clipboard.copy(projectDirectory);
	}

	runServer = () => {

		const projectDirectory = getMaidDirectory();
		const jupyter_folder = "/utils/data-science-cli/problems";

		const jupyterCommand = `jupyter notebook --notebook-dir=${projectDirectory}/${jupyter_folder}`;
		exec(jupyterCommand);
	}

	openJupyter = async ({ FILE = "/machine_learning/01_pandas.ipynb" } = {}) => {

		copyFileToTemp(FILE);

		const correctPrompt = new Confirm({
			name: 'notebook',
			message: "Was the notebook solved correctly?",
			initial: true });
		const response = await correctPrompt.run();
		if (response) {
			await increasePerformance("jupyter");
		}
		return response;

	}

	/**
	 * Opens a random jupyter notebook from the list of problems
	 * @returns {bool} if the problem was solved correctly
	 */
	openRandomJupyter = async () => {
		const selectedProblem = getRandomProblem();
		this.runServer();

		return this.openJupyter({ FILE: "/" + selectedProblem.problem });
	}


	/**
	 * Cleans the terminal
	 */
	cleanTerminal = () => {
		console.clear();
	}

	// Prompts y/n question to clean, if y, cleans.
	askToClean = async () => {

		// const response = question('clean', 'y/n', { type: 'confirm' });
		const cleanPrompt = new Confirm({
			name: 'clean',
			message: "Would you like me to clean up the terminal?", 
			initial: true });
		const response = await cleanPrompt.run();
		console.log(response)
		if (response) {
			this.cleanTerminal();
		}

	}

	/**
	 * Prints the day report based on the settings
	 * - Performance Report: A table report stating the counts of each feature
	 * - Weather Report: A bar chart of the weather for the next 7 days
	 * - Missing Report: A list of the missing features for the day
	 * 		- If the if `ask-if-algo-missing` is true, it will ask if the user wants to run the `algo` trainer (If the user haven't completed his first algorithm in the day.)
	 */
	dayReport = async () => {
		const todaydate = getToday()

		if (Settings?.report_show?.performance_summary) {
			this.say(`Performance Report: ${todaydate}`, false)
			await this.performanceReport();
		}

		if (Settings?.report_show?.weather ?? false) {
			this.say(`Weather Report: ${todaydate}`, false)
			await weatherReport();
		}

		if (Settings?.report_show?.missing_report) {
			this.say(`Missing Report: ${todaydate}, dsa enabled: ${true}`, false)
			await this.provideMissingReport({ ask_if_dsa_missing: Settings?.report_show?.ask_if_algo_missing ?? false });
		}
	}

	/**
	 * Prints the missing objectives
	 * !important: To prepopulate the msising report first!!
	 */
	provideMissingReport = async ({ ask_if_dsa_missing = false } = {}) => {
		try {
			
			if (!this.missingFeatReport) {
				const _ = await this.populateMissingReport();
			}
			
			if (ask_if_dsa_missing) {

				await this.requests_if_run_dsa_trainer(this.missingFeatReport);
			}
			if (Settings?.report_show?.obj_ournal) {
				const journal_notes = Settings.journal_notes;
				console.log(journal_notes);
			}
			
		}
		catch (err) {
			if (DEV_MODE) console.log("Error in provideMissingReport", err)
		}
	}

	/**
	 * if `algo` not included on the missing Feat Report: 
	 * 	- ask to run `algo`
	 * 	- if yes, run `algo`
	 * 
	 */
	requests_if_run_dsa_trainer = async (missingFeatReport) => {
		const algo_missing = missingFeatReport.includes(CONSTANTS.algo_name);
		if (algo_missing) {



			const dsaPrompt = new Confirm({ 
				name: 'dsa',
				message: "Daily DSA Missing; Run algorithms?",
				initial: true });
			const response = await dsaPrompt.run();
			if (response) {
				const dsaTrainer = new DSATrainer(

				);
				const dsa_is_correct = await dsaTrainer.showRecommendedProblems();

				if (dsa_is_correct) {
					await increasePerformance("algo");
				}
			}
		}
		return;
	}


	/**
	 *  precalculated asynchronous at the start, since usually the missing Feat report is to be shown at the end of the math thing.
	 *  */
	populateMissingReport = async () => {

		try {
			const res = await axios.get(`${APIDICT.DEPLOYED_MAID}/account/missing_performance_today/${Settings.account_id ?? 1}`)
			this.missingFeatReport = res.data;
		}
		catch (err) {
			if (DEV_MODE) {


				console.log("API call", `${APIDICT.DEPLOYED_MAID}/account/missing_performance_today/${Settings.account_id ?? 1}`)
				console.log("Error in populateMissingReport", err)
				if (Settings.show_http_errors) {
					console.log(err);
				}
			}
		}
	}


	performanceReport = async ({ version = "tables" } = {}) => {

		const res = await axios.get(`${APIDICT.DEPLOYED_MAID}/account/report/${Settings.account_id ?? 1}`, {
			headers: {
				'Accept-Encoding': 'application/json'
			}
		});

		const feat_rules = getObjectiveFeatures();
		let userPerformanceData = await res.data;

		function parseDecimalsColumns(userPerformanceData, columns = ["week_average", "week_average_exclude_today"]) {

			for (const column of columns) {

				for (const [key, value] of Object.entries(userPerformanceData?.[column])) {
					let message = parseFloat(value.toFixed(2));
					userPerformanceData[column][key] = message;
				}
			}
			return userPerformanceData;

		}

		userPerformanceData = parseDecimalsColumns(userPerformanceData, ["week_average", "week_average_exclude_today"]);
		function createFeaturesMap(feat_rules) {
			let feat_map = {};
			for (const [key, value] of Object.entries(feat_rules)) {
				feat_map[key] = 0;
			}
			return feat_map;
		}

		function updateRequirements(features_accomplished_today, feat_rules, userPerformanceData) {
			for (const [requirement_key, settings] of Object.entries(feat_rules)) {
				// Check if it requires a 'day' required performance
				if (settings.day) {
					// Then search for the performance, and give the difference between the required and the actual performed
					const day_requirement = settings.day;
					const day_performance = userPerformanceData?.["today"]?.[requirement_key] ?? 0;
					let day_difference = day_requirement - day_performance;
					if (day_difference < 0) {
						day_difference = "✅";
					}
					features_accomplished_today[`d: ${requirement_key}`] = { miss: day_difference, type: "day", req: day_requirement };


				}

				// Check if it requires a 'week' required performance
				if (settings.week) {
					// Then search for the performance, and give the difference between the required and the actual performed
					const week_requirement = settings.week;
					const week_performance = userPerformanceData?.['week_sum']?.[requirement_key] ?? 0;
					let week_difference = week_requirement - week_performance;
					if (week_difference < 0) {
						week_difference = '✅';
					}
					features_accomplished_today[`w: ${requirement_key}`] = { miss: week_difference, type: "week", req: week_requirement };
				}
			}
		}

		function visualsUpdateMap(features_accomplished_today) {
			// Do visual 
			// Consult for needed to accomplish today (as -number something or X as finished)
		}

		function filterProperties(userPerformanceData, properties = [], abreviations = { "week_sum_exclude_today": "week_sum_ex", "week_average_exclude_today": "weeK_avg_ex" }) {
			/**
			 * e.g. userPerformanceData in:
			 *  {'2023-05-20': { commits: 2, acad: 2, terms: 4 },
				today: { test: 12, tesrasd: 2, 'commits}': 6, 'terms}': 10 },
				month: {
					commits: 154,
					feat: 20,
					math_ss: 24,
					ref: 1,
					fix: 3,
					algo: 2,
					acad: 18,
					pro: 16,
					terms: 686,
					algo_w: 4,
					test: 12,
					tesrasd: 2,
					'commits}': 6,
					'terms}': 10
				}
				}
				and properties: [feat]
				should result in:
				{'2023-05-20': { feat: 20 },
				today: { feat: 20 },
				month: { feat: 20 }
				}
			*/
			// Iterate over the keys of the userPerformanceData object
			let filteredData = {};
			for (let date in userPerformanceData) {
				const naming = abreviations[date] ?? date;
				// For each key, create a new object that only contains the desired properties
				filteredData[naming] = properties.reduce((obj, prop) => {
					// If the current performance data has the current property, add it to the new object
					if (userPerformanceData[date][prop]) {
						obj[prop] = userPerformanceData[date][prop];
					}
					return obj;
				}, {});
			}

			// Return the filtered data
			return filteredData;
		}

		// Create the requirements per Day
		// const feat_accomplished_until_today = createFeaturesMap(feat_rules);
		const feat_accomplished_until_today = {};
		updateRequirements(feat_accomplished_until_today, feat_rules, userPerformanceData);

		// Filter where only userPerformanceData that are highlighted in the table_feat show are allowed "table_feat_show": ["commits", "feat", "algo_w", "pro", "math_ss"],
		// console.log(userPerformanceData)
		let filtered_data = filterProperties(userPerformanceData, Settings.table_feat_show);


		console.table(filtered_data);
		console.table(feat_accomplished_until_today);

	}


	printUserPerformanceDataSummary(userPerformanceData) {
		// Print this month
		// This week average
		// Today data

		const STATS = ['week_average_exclude_today', 'today', 'month'];

		for (const stat of STATS) {
			this.printPerformanceStat(stat, userPerformanceData);

		}

	}

	printPerformanceStat(label, userPerformanceData) {
		let statPerformance = userPerformanceData[label]
		statPerformance = formatObjectFeatures(statPerformance)

		if (DEV_MODE) console.log(label, statPerformance);
	}




	// Features is a list of FeatureExtraction
	barChartFeatures = (data, features, lasts = 2) => {
		/**
		 * Based on the key it should identify the 
		 */
		// const LASTXCHARS = 5;
		let bars = features.map((feature) => {
			// Attempt getting that from data or return a 0 as the bar information.
			const feat_value = data.hasOwnProperty(feature.feature_name) ? data[feature.feature_name][feature.feature_key] : 0;
			const feat_name_len = feature.feature_name.length;
			const lastCharacters = lasts > feat_name_len ? 0 : feat_name_len - lasts;
			const feat_name = lasts > 0 ? feature.feature_name.substring(lastCharacters) : feature.feature_name
			const bar = { key: feat_name, value: feat_value != undefined ? feat_value : 0, style: feature.style }
			return bar;

		})
		// KEEP for debugging. It will throw error if any of the values are undefined


		if (DEV_MODE) console.log(bar(bars))

	}



	services = async () => {

		const choices = [
			'get_credential',
			'forecast_costs',
			'usd_to_ars',
			'currency_exchange',
			'create_credential',
			'swap_double_single_quotes'
		]

		const CHOICE_CREDENTIAL = 0, CHOICE_COSTS = 1, CHOICE_USD_TO_ARS = 2, CHOICE_CURRENCY_EXCHANGE = 3, CHOICE_CREATE_CREDENTIAL = 4, CHOICE_SWAP_QUOTES = 5;

		const multiselect = new AutoComplete({
			name: 'ServiceOption',
			message: 'What to do on services?',
			choices: choices
		})

		let serviceSelected = await multiselect.run();

		// if services == get_credi

		console.log("service Selected", serviceSelected);
		if (serviceSelected == choices[CHOICE_CREDENTIAL].value && Settings.account_settings.access_credentials_enabled) {

			console.log('Retrieve credentials for...')
			const creds = await axios.get(`${APIDICT.DEPLOYED_MAID}/services`, {
				headers: {
					'Accept-Encoding': 'application/json'
				}
			});
			const credentials = await creds.data;
			const cred_names = getCredentialNames(credentials)
			const credentialSelect = new AutoComplete({
				name: 'credentialSelect',
				message: 'Which Credential?',
				choices: cred_names
			})
			const credentialNameSelected = await credentialSelect.run()
			const credentialSelected = getCredentialInformation(credentials, credentialNameSelected);
			console.log(credentialSelected);
			console.log(`Password copied to clipboard, ${credentialSelected.password}`)
			clipboard.copy(credentialSelected.password)

			// Show credentials available

		} else if (serviceSelected == choices[CHOICE_USD_TO_ARS].value) {
			this.createConversion();
		} else if (serviceSelected == choices[CHOICE_CURRENCY_EXCHANGE].value) {
			// Prompt from what to what to exchange.

			const fromCurrency = new AutoComplete({
				name: 'fromCurrency',
				message: 'Which Currency from?',
				choices: Object.keys(constants.CURRENCY_SIMBOLS)
			})

			const toCurrency = new AutoComplete({
				name: 'toCurrency',
				message: 'Which Currency to?',
				choices: Object.keys(constants.CURRENCY_SIMBOLS)
			})

			let fromCurrencySelected = await fromCurrency.run();
			let toCurrencySelected = await toCurrency.run();

			this.createConversion(fromCurrencySelected, toCurrencySelected);

		}
		else if (serviceSelected == choices[CHOICE_CREATE_CREDENTIAL].value) {


			const question = [
				{
					type: 'input',
					name: 'name',
					message: 'Service name?'
				},
				{
					type: 'password',
					name: 'password',
					message: 'password?'
				},
				{
					type: 'input',
					name: 'account_user',
					message: 'account user?'
				}
			]

			let answers = await prompt(question)


			const dataToPost = { "name": answers.name, "password": answers.password, "account_user": answers.account_user };

			const res = await axios.post(`${APIDICT.DEPLOYED_MAID}/services`, dataToPost);
			const response_data = res.data;
			response_data.password = "*********************";
			// this.say(response_data);
			console.log("created service", response_data);
		}
		else if (serviceSelected == choices[CHOICE_SWAP_QUOTES].value) {
			let input = await Input({
				name: choices[CHOICE_SWAP_QUOTES].value,
				message: "Enter string to convert"
			});
			input.replaceAll("'", "$_'")
			input.replaceAll("\"", "$_\"")
			input.replaceAll("$_\"", "'")
			input.replaceAll("$_'", "\"")

		}
		else {
			console.log(choices[CHOICE_CREDENTIAL]);
			console.log(serviceSelected);
		}


	}



	ask = async () => {
		// Asking some random fnction

		const choices = [
			'currency symbol for...',
			// 'forecast_costs',
			// 'usd_to_ars',
			// 'currency_exchange'
		]

		const CHOICE_CURRENCY = 0;

		const multiselect = new AutoComplete({
			name: 'question',
			message: 'What do you want to know?',
			choices: choices
		})

		let serviceSelected = await multiselect.run();

		// if services == get_credi

		console.log("service Selected", serviceSelected);
		if (serviceSelected == choices[CHOICE_CURRENCY].value) {

			const currencySelect = new AutoComplete({
				name: 'currency',
				message: 'Which currency?',
				choices: Object.values(constants.CURRENCY_SIMBOLS)
			})

			let currencySelected = await currencySelect.run();
			this.say(`${currencySelected} => ${getKeyByValue(constants.CURRENCY_SIMBOLS, currencySelected)}`);

		}


	}

	async createConversion(from = 'USD', to = 'ARS') {

		var config = {
			method: 'get',
			url: `${APIDICT.CURRENCY_EXCHANGE}/convert?to=${to}&from=${from}&amount=1`,
			headers: {
				'apikey': APIDICT.CURRENCY_EXCHANGE_KEY,
				'Accept-Encoding': 'application/json'
			}

		};
		const res = await axios(config);
		const exchangeRes = await res.data
		this.say(`${from} to ${to} during ${exchangeRes.date} is around ${exchangeRes?.info?.rate} ${constants.CURRENCY_SIMBOLS[to]} per ${constants.CURRENCY_SIMBOLS[from]}`)

	}

}




/**
 * Based on the speciffied feature it returns the corresponsive barcharts
 */
populateLastDaysFeaturesBarCharts = (days = 7, feature = 'feat') => {

	const lastWeekInclusive = getArrayLastXDays(days);
	const todayDay = lastWeekInclusive[lastWeekInclusive.length - 1];
	const yesterdayDay = lastWeekInclusive[lastWeekInclusive.length - 2];
	return lastWeekInclusive.map(date => {
		let bgcolor = bg('white');
		if (todayDay == date) {
			bgcolor = bg('yellow');
		} else if (date == yesterdayDay) {
			bgcolor = bg('blue');
		}
		return new FeatureExtraction(date, feature, bgcolor);
	})

}

/**
 * Expected output: {month: {}, lastweek: {}, yesterday: {}, today: {}}
 */
populateLastDaysPerformanceReport = (days = 7) => {
	const lastWeekInclusive = getArrayLastXDays(days);


}



getArrayLastXDays = (days = 7) => {
	const pastDays = [...Array(days).keys()].map(index => {
		const date = new Date();
		date.setDate(date.getDate() - (days - 1 - index));

		return date.toISOString().slice(0, 10);
	});

	// console.log(pastDays);
	return pastDays;
}

/**
Waiting for pgAdmin 4 to start... * Increase the performance of a feature; Day performances are such as commits, features, etc.
 * @param {str} feature_name: The name of the feature to increase
 * @param {int} increaseBY: The amount to increase the feature by; default 1
 * @param {bool} debug ?= false : If to whether to debug api responses, etc.; default false
 * @param {int} account_id ?= 1 : The account id to increase the performance; default Settings account_id or 1
 * 
 */
increasePerformance = async (feature_name, increaseBY = 1, debug = true, account_id = Settings.account_id ?? 1) => {
	try {
		console.log(`Increasing performance ${feature_name} for ${account_id}`)
		const res = await axios.post(`${APIDICT.DEPLOYED_MAID}/day_performance/${feature_name}?increase_score=true&value=${increaseBY}&account_id=${account_id}`)
		if (debug) console.log(res.data);
	} catch (err) {
		if (debug) console.warn(err);
	}

}


/**
 * Updates the count of times a concept has been practiced e.g. `algebra-problem-1` or 'js-how-to-loop'
 * @param {str} problem_name: The name of the problem to update
 * @param {bool} success ?= true : If to whether to increase the success count or the fail count
 * @param {bool} debug ?= false : If to whether to debug api responses, etc.
 * @param {int} account_id ?= 1 : The account id to increase the performance; default Settings account_id or 1
 * 
 * @returns {"message": f"Success updating {concept_term}, {conceptSelected.correct_times}"}
 */
updateConcept = async (problem_name, success = true, debug = false, account_id = Settings.account_id ?? 1) => {
	const URL = `${APIDICT.DEPLOYED_MAID}/concept_metadata/${problem_name}?success=${success}&account_id=${account_id}`
	try {
		const res = await axios.post(URL)
		if (debug) console.log(res.data)
	}
	catch (err) {
		console.warn('error in updateConcept');
	}
}


/**
 * based on the `objectives_features` at Settings returns in the format of:
 * 
	const feat_rules = {
		terms: {
			description: "Terminologies practiced",
			week: 100
		},
		pro: {
			description: "Professional Projects",
			week: 3 * 5
		},
		feat: {
			description: "Features for personal projects",
			week: 1 * 5 + 2 * 3
		},
		acad: {
			description: "Academic Projects / Assignments / notes added",
			week: 1 * 5
		}
		...
	}
	 */
function getObjectiveFeatures() {

	const feat_rules = Settings.objectives_features ?? [];
	/** Receives in the format of:
	 * 
	 * [
		{
			"feature_key": "commits",
			"description": "The number of git commits to be done",
			"req_type": "day",
			"requirement": 3
		},
		{
			"feature_key": "feat",
			"description": "The amount of Personal Project Features to be released",
			"req_type": "week",
			"requirement": 11
		},
	 */

	// Format in the expected format.
	let feat_map = {};
	for (const feat_rule of feat_rules) {
		// connect the feature lapse to the requiremnett
		feat_map[feat_rule.feature_key] = {}
		feat_map[feat_rule.feature_key][feat_rule.req_type] = feat_rule.requirement;
	}

	return feat_map;

}

function getKeyByValue(object, value) {
	return Object.keys(object).find(key => object[key] === value);
}

const getCredentialNames = (credentialDict) => {
	return credentialDict.map(cred => {
		return cred.name
	})
}

/**
 * Retrieves fromt the json the proper credentials as n object
 */
const getCredentialInformation = (credentialsDict, credential_name) => {

	res = credentialsDict.filter(
		(cred) => cred.name == credential_name
	)
	return res.length > 0 ? res[0] : {};
}


const getToday = () => {
	// Returns as string format: "2022/12/09" 
	return new Date().toJSON().slice(0, 10).replace(/-/g, '/');
}


const getTalk = async flags => {
	if (flags.type == 'chuck') {
		const res = await axios.get(APIDICT.CHUCK, {
			headers: {
				'Accept-Encoding': 'application/json'
			}
		});
		message = res.data.value;
	} else if (flags.type) {
		message = flags.type;
	}
	return message;
};




const weatherReport = async () => {
	const res = await axios.get(APIDICT.WEATHER, {
		headers: {
			'Accept-Encoding': 'application/json'
		}
	});
	weatherData = new WeatherInformation(res);
	console.log(weatherData.json)
	// console.log(weatherData.days_report)
	weatherData.chartWeatherBar()


}

class CommitCategoryType {
	constructor(code, icon_list, feature_name = "") {
		this.code = code;
		this.icon_list = icon_list;

		if (feature_name == "") {
			this.features_name = code;
		} else {
			this.feature_name = feature_name;
		}

	}

	randomIcon() {
		return get_random(this.icon_list);
	}

	toString() {
		return this.code;
	}

};


const getCommitCategories = () => {
	let commitCategories = {}
	const commit_categories_settings = Settings.commit_categories ?? [];

	for (const commit_categories_setting_row of commit_categories_settings) {
		const code_key = commit_categories_setting_row.code;
		commitCategories[code_key] = new CommitCategoryType(code_key, commit_categories_setting_row.icon_list, commit_categories_setting_row.code);
	}

	return commitCategories;
}

/**
	 * Based on a term and response written by the user it should post things in the comments based on that.
		* @param {Term Structure} term_selected: The term which response was answered
		* @param {str} user_res: Response answered by the user on the terminal
		* @param {bool} debug ?= False : If to whether to debug api responses, etc.
	 */
const postCommentFromTerm = async (term_selected, user_res, debug = false) => {
	/**Expected Body Structure: for `https://jmmgskxdgn.us-east-1.awsapprunner.com/comment`
	 * {
		"account_id": 0,
		"body": "string",
		"title": "string",
		"concept_slug": "string"
		}
	 */

	if (debug) console.log("Posting comment", term_selected, user_res)


	try {

		const data = {
			'account_id': Settings.account_id ?? 1, //1
			'body': user_res ?? "",
			'title': term_selected ?? "title",
			'concept_slug': term_selected ?? "slug"
		}

		axios({
			method: 'post',
			url: `${APIDICT.DEPLOYED_MAID}/comment`,
			headers: {},
			data: data
		});

		if (debug) console.log("Comment has been made", data);


	} catch (err) {
		if (debug) console.log("Probably no connection, comment has not been made")
		if (debug) {
			console.log(err)
		}

	}
}

/**
 * Pushes to origin with a commit message
 * If it contains any of the specials categories (configurable in settings.js) it will log it in the feature (habit) database.
 * @param {bool} addMaidEmoji ?= true : If to whether to add a maid emoji
 * @param {bool} addCommitEmoji ?= true : If to whether to add a commit emoji
 
 * @param {bool} debug ?= false : If to whether to debug api responses, etc.
 * @param {List: [date: comment]} comments_to_populate ?= [] : List of comments to populate
 * 
 * @Setting {bool} log_special_categories ?= true : Setting to whether to log special categories
 * 
 * @returns {List: [date: comment]}
 * 
 */
const commitpush = async (addCommitEmoji = true, { debug = false, comments_to_populate = [] } = {}) => {


	let commitMessage = process.argv[3];
	if (debug) {
		console.log(commitMessage)

	}
	if (commitMessage == undefined) {
		commitMessage = CONSTANTS.default_commit_message;
	}




	// If any category found then increase the score please.
	commitCat = commitCategory(commitMessage, true);
	// Log special categories

	if (Settings.blog_special_commits ?? false) {
		comments_to_populate = await logCommitIfSpecialCategory(commitMessage, commitCat, comments_to_populate, { print_previous_commits: true });
	}

	// Removed await statement for hopes of faster responsee load
	increasePerformance("commits");
	if (commitCat?.code) {
		increasePerformance(commitCat.code);
		if (addCommitEmoji) commitMessage = commitMessage + " " + commitCat.randomIcon();
	}


	commitMessage = appendQuotes(commitMessage + " " + getRandomMaidEmoji());

	exec(`git add --all && git commit -m ${commitMessage} && git push origin HEAD `);
	if (true) console.log(`Pushed to origin with commit message: ${commitMessage}`);

	return { comments_to_populate: comments_to_populate, commit_category: commitCat, commit_message: commitMessage };
}

/**
 * 
 * @param {string} term the term to search for comments
 * @param {number} count the number of comments to retrieve
 * @returns {Map<date:<date: comment>>}
 */
const getComments = async (term, count = 5) => {

	if (term == undefined || term == "") {
		return {};
	}
	const URL = `${APIDICT.DEPLOYED_MAID}/comment/term/${term}?format_simple=true&limit=${count}`;
	try {
		const res = await axios.get(URl, {
			headers: {
				'Accept-Encoding': 'application/json'
			}
		}
		);

		return res;
	}
	catch (err) {
		console.log("Error in getComments", URL)

	}
	// return res.data;
}


/** 
 * Prints the comments in a nice format
 * @param {Map<date:<date: comment>>} comments e.g. [
  { '2023-04-07': 'feat: debug' },
  { '2023-04-07': 'feat: special category' }
]
*/
const printComments = (comments) => {


	for (const row in comments) {
		const obj = comments[row]
		console.log(`${chalk.hex(CONSTANTS.CUTEBLUE).inverse(`${Object.keys(obj)?.[0]} ` ?? "date")} ${Object.values(obj)?.[0] ?? "1"}`);
	}
}



/**
 * logs commit message in comments database if category is special
 * @param {string} commitMessage message to commit
 * @param {ECommitCategory} category category of the commit
 * @param {bool} print_previous_commits ?= true : If to whether to print previous commits
 * @param {bool} debug ?= false : If to whether to debug api responses, etc.
 * @returns {List: [date: comment]}
 */
const logCommitIfSpecialCategory = async (commitMessage, category, comments_to_populate = [], { print_previous_commits = true, debug = false } = {}) => {
	// if (true) console.log("Logging commit message in comments database?", category.code, special_categories, special_categories.includes(category.code))

	// Log the commit message in the comments database
	postCommentFromTerm(category.code, commitMessage);
	const res = await getComments(category?.code ?? "");
	comments_to_populate = res?.data ?? '';
	// console.log("comments_to_populate | special category", comments_to_populate)


	return comments_to_populate;

}



/**
 * 
 * @param {string} commitMessage Message to commit
 * @param {bool} strict If true, it will only detect categories when they appear followed by '|' e.g. 'feat |'
 * @returns {string} category code e.g. 'feat'
 */
const commitCategory = (commitMessage, strict = false, { debug = false } = {}) => {


	for (category of Object.values(getCommitCategories())) {
		if (debug) console.log("commitMessage", commitMessage)

		if (strict) {
			if (commitMessage.includes(category.code + " |")) {
				return category;
			}

		}
		else {
			if (commitMessage.includes(category.code)) {
				return category;
			}
		}
	}
	return ""; //No category at all.
}

const autorelease = () => {
	// Maid can auto-release herself

	let commitMessage = process.argv[3];
	if (commitMessage == undefined) {
		exec(`maid coa && make new m ="random commit"`);
	} else {

		exec(`maid coa && make new m="${commitMessage}"`);
	}
}

module.exports = {
	getTalk, commitpush, autorelease, printComments,
	Maid, getToday, FlashQuizzer, increasePerformance,
	commitCategory, logCommitIfSpecialCategory, postCommentFromTerm, getComments
};