import { Deferred } from "./deferred";
import { forEach, has, isArray } from "lodash";
import Vue from "vue";

/**
 * The task which is completed once the client is ready
 */
export const clientReadyTask = new Deferred<void>();

/**
 * Create a logger instance that prepends all messages
 * with the specified label.
 * 
 * @param label The label
 * @param debug Whether the logger is debug
 * @returns Logging function
 */
export function logger(label: string, debug = false): (...args: any[]) => void {
	return (...args: any) => {
		(debug ? console.debug : console.log)(`[${label}]`, ...args);
	};
}

/**
 * Parse the given string to a number, and simply
 * return any number passed in.
 * 
 * @param value The input
 * @returns The number
 */
export function cleanInt(value: string|number): number {
	return typeof value == 'number' ? value : parseInt(value);
}

/**
 * Format the given time in milliseconds for human readability
 * 
 * @param value The time in milliseconds
 * @param format The time format
 * @returns Formatted string
 */
export function formatTime(value: number|string, format: string): string {
	throw new Error("Not implemented");
	// return dayjs(parseInt(value as any)).format(vue.$t(format) as string);
}

/**
 * Parse the given slug string into a numeric id.
 * 
 * Slugs follow the format of a hyphen separated string of
 * words, with the first segment being a number.
 * 
 * @param slug The slug string
 * @returns decoded id
 */
export function parseSlug(slug: string): number|undefined {
	return parseInt(slug.split('-')[0]) || undefined;
}

/**
 * Check if the current environment is loaded into Electron
 * 
 * Inspired by https://github.com/cheton/is-electron
 * 
 * @returns The result
 */
export function isElectron(): boolean {
	if(typeof window !== 'undefined' && typeof window.process === 'object' && (window.process as any).type === 'renderer') {
		return true;
	}

	if(typeof navigator === 'object' && typeof navigator.userAgent === 'string' && navigator.userAgent.indexOf('Electron') >= 0) {
		return true;
	}

	return false;
}

/**
 * Expect the given object to contain the specified
 * set of fields, and if not, an error is thrown.
 * 
 * @param obj The instance
 * @param fields The fields to check
 */
export function expect(obj: any, fields: string[]) {
	if(typeof obj != 'object') return new Error('Instance is not an object');

	for(const field of fields) {
		if(!has(obj, field)) {
			throw new Error(`Field ${field} is missing in instance`);
		}
	}
}

/**
 * Prop validating version of "expect", returns the validation
 * function when called.
 * 
 * @param fields The fields to check
 */
export function expectProp(fields: string[]) : (value: any) => boolean {
	return (value) => {
		if(typeof value != 'object') return false;

		for(const field of fields) {
			if(!has(value, field)) {
				console.error(`Field ${field} is missing in instance`);
				return false;
			}
		}

		return true;
	};
}

/**
 * Prop validating version of "expect", returns the validation
 * function when called. Similar to #expectProp, but expects an
 * array of which each element will be validated.
 * 
 * @param fields The fields to check
 */
export function expectArrayProp(fields: string[]) : (value: any) => boolean {
	return (value) => {
		if(!isArray('object')) return false;

		forEach(value, (item, i) => {
			for(const field of fields) {
				if(!has(item, field)) {
					console.error(`Field ${field} is missing in index ${i}`);
					return false;
				}
			}
		});

		return true;
	};
}

/**
 * Create a sorting function which adopts the order of
 * the given proxy array
 * 
 * @param proxy The proxy array
 * @param key An optional key extractor
 * @returns 
 */
export function proxyComparator(proxy: any[], key?: string): (left: any, right: any) => number {
	return (left, right) => {
		const lk = key ? (left as any)[key] : left;
		const rk = key ? (right as any)[key] : right;

		return proxy.indexOf(lk) - proxy.indexOf(rk);
	};
}

/**
 * Build a path from the given parts
 * 
 * @param parts The parts
 * @returns The path
 */
export function buildPath(...parts: string[]): string {
	let builder = '';
	let i = 0;

	for(const part of parts) {
		builder += part;

		if(!builder.endsWith('/') && i < parts.length - 1) {
			builder += '/';
		}

		i++;
	}

	return builder;
}

/**
 * Returns the first occurence of the given key in the
 * specified list of objects.
 * 
 * @param key The key to search for
 * @param objects The objects to search
 * @returns The value
 */
export function getFirstValue(key: string, objects: any[]): any {
	for(const obj of objects) {
		if(has(obj, key)) {
			return obj[key];
		}
	}

	return undefined;
} 

/**
 * Focus the given vue component input
 * 
 * @param element The vue component
 */
export function focusInput(component: Vue) {
	component?.$el?.querySelector('input')?.focus();
}

/**
 * A function that converts a hex color to a rgb color
 * 
 * @param hex The hex color string
 * @returns The RGB color components
 */
export function hexToRgb(hex: string): {r: number, g: number, b: number} {
	if(hex.length === 4) {
		hex = hex[0] + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];
	}

	const r = parseInt(hex.substring(1, 3), 16);
	const g = parseInt(hex.substring(3, 5), 16);
	const b = parseInt(hex.substring(5, 7), 16);

	return { r, g, b };
}

/**
 * A function which returns true if the given HEX color is dark
 * 
 * @param hex The hex color string
 * @returns Whether the color is dark
 */
export function isColorDark(hexColor: string): boolean {
	const rgb = hexToRgb(hexColor);
	const luma = 0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b;

	return luma < 142;
}

/**
 * A function which downloads a textfile from an unparsed / text source
 * 
 * @param data 
 * @param filename 
 */
export function downloadText(data: string, filename: string) {

	// download a exported version of the template
	const element = document.createElement('a');

	element.href = 'data:text/json;charset=utf-8,' + encodeURIComponent(data);
	element.target = '_blank';
	element.download = filename;
				
	element.click();
}

/**
 * A function which shows the explorer dialog of the system
 * where you can select a file to import
 */
export function selectFileFromExplorer(filter: string, callback: Function): void {

	let file: File|null = null;

	//
	const input = document.createElement('input');

	input.type = 'file';
	input.accept = filter;
	input.style.display = 'none';

	// handling the selected file
	input.onchange = function(e: Event) {
		file = (e.target as HTMLInputElement)!.files![0];
		callback(file);
	};

	input.click();
}