import {
	isStringIndexedInterface,
} from "qrc:/js/lib/generated/typeguard";

import {
	assert,
	exhaustiveStringTuple,
	getKeysOfObject,
	isArray,
	isNumber,
	isString,
	readSetting,
	writeSetting,
} from "./utils";

export type ScaleValueMode = "none" | "relative" | "absolute";

export function isScaleValueMode(arg: unknown): arg is ScaleValueMode {
	return isString(arg) && exhaustiveStringTuple<ScaleValueMode>()(
		"none",
		"relative",
		"absolute",
	).some(item => item === arg);
}

export interface CalcSettings {
	/**
	 * Whether the values should be interpreded as relative base values
	 * w.r.t. the associated multiplicity or as absolute scale values.
	 */
	scaleValueMode: ScaleValueMode;

	/**
	 * The scale values
	 */
	baseScaleValues: number[];
}

const defaultCalcSettings: CalcSettings = {
	baseScaleValues: [
		5,
		50,
	],
	// Previous implicit behaviour
	scaleValueMode: "relative",
};

const typeGuardMap: {[key in keyof CalcSettings]: (arg: unknown) => arg is CalcSettings[key]} = {
	baseScaleValues: (arg: unknown) : arg is number[] => isArray(arg, isNumber),
	scaleValueMode: isScaleValueMode,
};

function isType<Key extends keyof CalcSettings>(key: Key, arg: unknown): arg is CalcSettings[Key] {
	return typeGuardMap[key](arg);
}

export function isCalcSettings(arg: unknown): arg is CalcSettings {
	return isStringIndexedInterface(arg) && getKeysOfObject(defaultCalcSettings)
		.every(key => isType(key, arg[key]));
}

function isPartialExportSettings(arg: unknown): arg is Partial<CalcSettings> {
	return isStringIndexedInterface(arg) && getKeysOfObject(defaultCalcSettings)
		.every(key => !(key in arg) || isType(key, arg[key]));
}

const settingsKey = "calc_settings";

function updateWithDefaultValue<Key extends keyof CalcSettings>(key: Key, value: Partial<CalcSettings>): Partial<CalcSettings> {
	value[key] = defaultCalcSettings[key];
	return value;
}

export function readCalcSettings(): CalcSettings {
	const incompleteSettings = readSetting(settingsKey, defaultCalcSettings, isPartialExportSettings);
	const completeSettings = getKeysOfObject(defaultCalcSettings)
		.filter(key => !isType(key, incompleteSettings[key]))
		.reduce((acc: Partial<CalcSettings>, key) => updateWithDefaultValue(key, acc), incompleteSettings);
	assert(isCalcSettings(completeSettings), "Expecting complete Export settings");
	return completeSettings;
}

export function writeCalcSettings(value: CalcSettings) : void {
	writeSetting(settingsKey, value);
}
