import {settingsKey} from "./settings.js";
import {getDefaultDashMultiplier, getDefaultSpeedAttribute} from "./systems.js";

/**
 * Base class for all speed providers.
 * If you want to offer a speed provider in your system/module you must derive this class.
 * Each speed provider must at least implement
 */
export class SpeedProvider {
	/**
	 * Returns an array of colors used by this speed provider. Each color corresponds to one speed that a token may have.
	 * Each color must be an object with the following properties:
	 * - id: A value that identfies the color. Must be unique for each color returned.
	 * - default: The color that is used to highlight that speed by default.
	 * - name: A user readable name for the speed represented by the color. This name is used in the color configuration dialog. Drag Ruler will attempt to localize this string using `game.i18n`
	 *
	 * Of these properties, id and defaultColor are required. name is optional, but it's recommended to set it
	 *
	 * Implementing this method is required for all speed providers
	 */
	get colors() {
		throw new Error("A SpeedProvider must implement the colors function");
	}

	/**
	 * Returns an array of speeds that the token passed in the arguments this token can reach.
	 * Each range is an object that with the following properties:
	 * - range: A number indicating the distance that the token can travel with this speed
	 * - color: The id (as defined in the `colors` getter) of the color that should be used to represent this range
	 *
	 * Implementing this method is required for all speed providers
	 */
	getRanges(token) {
		throw new Error("A SpeedProvider must implement the getRanges function");
	}

	/**
	 * Returns an array of configuration options for this module. The settings will be shown in the Speed Provider Settings of Drag Ruler.
	 * Each configuration option is an object that has the same attributes as a native foundry setting passed to `game.settings.register`,
	 * except for these exceptions:
	 * - id: A string that identifies the setting. Must be unique for each setting returned. This id will be used to fetch the setting.
	 * - config: This property is not supported by Drag Ruler module settings. Use foundries native settings instead if you need settings that don't show up in the configuration dialog.
	 *
	 * Implementing this method is optional and only needs to be done if you want to provide custom provider settings
	 */
	get settings() {
		return [];
	}

	/**
	 * Returns the default color for ranges that a token cannot reach.
	 *
	 * Implementing this method is optional and only needs to be done if you want to provide a custom default for that color.
	 */
	get defaultUnreachableColor() {
		return 0xff0000;
	}

	/**
	 * Returns a boolean indicating whether this token will use a Ruler or not.
	 * If this is returns `false` for a token Drag Ruler will be disabled for that token. Dragging a token for which this function
	 * returns false will behave as if Drag Ruler wasn't installed.
	 * If usesRuler returns `false` it's guranteed that the `getRanges` function won't be called for that token.
	 *
	 * Implementing this method is optional and only needs to be done if you want to disable Drag Ruler for some tokens.
	 */
	usesRuler(token) {
		return true;
	}

	/**
	 * This hook is being called after Drag Ruler has updated the movement history for one or more tokens.
	 * It'll receive an array of tokens that have been updated.
	 * If your speed provider is storing any additional values that are relevant for the movement history, this function should
	 * await until those updates have completed inside foundry.
	 */
	async onMovementHistoryUpdate(tokens) {}

	/**
	 * Returns the value that is currently set for the setting registered with the provided settingId.
	 *
	 * This function shouldn't be overridden by speed provider implementations. It can be called to fetch speed provider specific settings.
	 */
	getSetting(settingId) {
		try {
			return game.settings.get(settingsKey, `speedProviders.${this.id}.setting.${settingId}`);
		} catch (e) {
			if (this.settings.some(setting => setting.id === settingId)) {
				throw e;
			}
			throw new Error(
				`Drag Ruler | "${settingId}" is not a registered setting for "${this.id}". If you're the module/system developer, please add it to the return values of your Speed Providers "get settings()" function.`,
			);
		}
	}

	/**
	 * Constructs a new instance of he speed provider
	 *
	 * This function should neither be called or overridden by speed provider implementations
	 */
	constructor(id) {
		this.id = id;
	}
}

export class GenericSpeedProvider extends SpeedProvider {
	get colors() {
		return [
			{id: "walk", default: 0x00ff00, name: "drag-ruler.genericSpeedProvider.speeds.walk"},
			{id: "dash", default: 0xffff00, name: "drag-ruler.genericSpeedProvider.speeds.dash"},
		];
	}

	getRanges(token) {
		const speedAttribute = this.getSetting("speedAttribute");
		if (!speedAttribute) return [];
		const tokenSpeed = parseFloat(getProperty(token, speedAttribute));
		if (tokenSpeed === undefined) {
			console.warn(
				`Drag Ruler (Generic Speed Provider) | The configured token speed attribute "${speedAttribute}" didn't return a speed value. To use colors based on drag distance set the setting to the correct value (or clear the box to disable this feature).`,
			);
			return [];
		}
		const dashMultiplier = this.getSetting("dashMultiplier");
		if (!dashMultiplier) return [{range: tokenSpeed, color: "walk"}];
		return [
			{range: tokenSpeed, color: "walk"},
			{range: tokenSpeed * dashMultiplier, color: "dash"},
		];
	}

	get settings() {
		return [
			{
				id: "speedAttribute",
				name: "drag-ruler.genericSpeedProvider.settings.speedAttribute.name",
				hint: "drag-ruler.genericSpeedProvider.settings.speedAttribute.hint",
				scope: "world",
				config: true,
				type: String,
				default: getDefaultSpeedAttribute(),
			},
			{
				id: "dashMultiplier",
				name: "drag-ruler.genericSpeedProvider.settings.dashMultiplier.name",
				hint: "drag-ruler.genericSpeedProvider.settings.dashMultiplier.hint",
				scope: "world",
				config: true,
				type: Number,
				default: getDefaultDashMultiplier(),
			},
		];
	}
}
