import { FConfiguration } from "./f_configuration.js";
import { FConfigurationException } from "./f_configuration_exception.js";
import { FConfigurationValue } from "./f_configuration_value.js";
import { FExceptionArgument } from "../exception/index.js";

export class FConfigurationChain extends FConfiguration {
	public get sourceURI(): URL {
		return this._sourceURI;
	}
	public get namespaceFull(): string | null {
		return this._configurations[0]!.namespaceFull;
	}
	public get namespaceParent(): string | null {
		return this._configurations[0]!.namespaceParent;
	}
	public get keys(): ReadonlyArray<string> {
		const union = new Set<string>();
		for (const item of this._configurations) {
			for (const key of item.keys) {
				union.add(key);
			}
		}
		return Object.freeze([...union]);
	}

	public constructor(...configurations: ReadonlyArray<FConfiguration>) {
		super();

		if (configurations.length === 0) {
			throw new FExceptionArgument("At least one configuration required for chain.", "configurations");
		}

		const innerCommaSeparatedSourceURIs: string = configurations
			.map(s => s.sourceURI.toString())
			.map(s => encodeURIComponent(s))
			.join(",");

		const sourceURI: string = `configuration:chain?sources=${innerCommaSeparatedSourceURIs}`;

		this._sourceURI = new URL(sourceURI);
		this._configurations = Object.freeze(configurations);
	}

	public getArray(key: string, indexesName: string = FConfiguration.DEFAULT_INDEXES_KEY): Array<FConfiguration> {
		const arrayNS = this.getNamespace(key);
		const arrayIndexes: Array<string> = arrayNS.get(indexesName).asString
			.split(" ")
			.filter(s => s !== "");

		const arrayNamespaces: Array<FConfiguration> = arrayIndexes.map(s => {
			return arrayNS.getNamespace(s);
		});

		return arrayNamespaces;
	}

	public getNamespace(namespaceFull: string): FConfiguration {
		const innerConfiguration: FConfiguration | null = this.findNamespace(namespaceFull);
		if (innerConfiguration !== null) {
			return innerConfiguration;
		}

		// Force underlying config to raise error
		for (const configuration of this._configurations) {
			configuration.getNamespace(namespaceFull);
		}

		// just a guard, should not happens if underlying configuration is implemented correctly
		throw new FConfigurationException(
			`Namespace was not found in the configuration.`,
			namespaceFull
		);
	}

	public get(key: string, defaultData?: string | null): FConfigurationValue {
		const foundValue: FConfigurationValue | null = this.find(key);
		if (foundValue !== null) {
			return foundValue;
		}

		const namespaceFull = this.namespaceFull;
		const fullKey = namespaceFull !== null ? `${namespaceFull}.${key}` : key;

		if (defaultData !== undefined) {
			const value: FConfigurationValue = FConfigurationValue.factory(
				fullKey, defaultData, null, null
			);
			return value;
		} else {
			throw new FConfigurationException("Current configuration does not have such key. Check your configuration.", fullKey);
		}
	}

	public findNamespace(namespaceFull: string): FConfiguration | null {
		const innerConfigurations: Array<FConfiguration> = [];
		for (const configuration of this._configurations) {
			if (configuration.hasNamespace(namespaceFull)) {
				innerConfigurations.push(configuration.getNamespace(namespaceFull));
			}
		}
		if (innerConfigurations.length === 0) {
			return null;
		}
		return new FConfigurationChain(...innerConfigurations);
	}

	public find(key: string): FConfigurationValue | null {
		for (let itemIndex = 0; itemIndex < this._configurations.length; ++itemIndex) {
			const configurationItem: FConfiguration = this._configurations[itemIndex]!;
			if (configurationItem.has(key)) {
				return configurationItem.get(key);
			}
		}

		return null;
	}

	public hasNamespace(namespaceFull: string): boolean {
		for (const configuration of this._configurations) {
			if (configuration.hasNamespace(namespaceFull)) {
				return true;
			} else if (configuration.has(namespaceFull)) {
				const isMaskedNamespace = configuration.get(namespaceFull).isNull;
				if (isMaskedNamespace) {
					// This is masked namespace
					return false;
				}
			}
		}
		return false;
	}

	public has(key: string): boolean {
		for (const configuration of this._configurations) {
			if (configuration.has(key)) { return true; }
		}
		return false; // no one configurations contains key
	}

	private readonly _sourceURI: URL;

	/**
	 * List of inner configurations. Values from first more important!
	 */
	private readonly _configurations: ReadonlyArray<FConfiguration>;

	// private static throwWrongKeyError(key: string, parentNamespace: string | null): never {
	// 	if (parentNamespace !== null) {
	// 		const fullKey: string = `${parentNamespace}.${key}`;
	// 		throw new FConfigurationException(
	// 			`A value was not found in current configuration.`,
	// 			fullKey
	// 		);
	// 	}
	// 	throw new FConfigurationException(
	// 		`A value was not found in current configuration.`,
	// 		key
	// 	);
	// }
}
