import type IBrowserSettings from './types/IBrowserSettings.js';
import type IOptionalBrowserSettings from './types/IOptionalBrowserSettings.js';
import DefaultBrowserSettings from './DefaultBrowserSettings.js';

/**
 * Browser settings utility.
 */
export default class BrowserSettingsFactory {
	/**
	 * Returns browser settings.
	 *
	 * @param [settings] Browser settings.
	 * @returns Settings.
	 */
	public static createSettings(settings?: IOptionalBrowserSettings): IBrowserSettings {
		if (settings) {
			this.validate(DefaultBrowserSettings, settings);
		}
		return {
			...DefaultBrowserSettings,
			...settings,
			navigation: {
				...DefaultBrowserSettings.navigation,
				...settings?.navigation
			},
			navigator: {
				...DefaultBrowserSettings.navigator,
				...settings?.navigator
			},
			timer: {
				...DefaultBrowserSettings.timer,
				...settings?.timer
			},
			fetch: {
				...DefaultBrowserSettings.fetch,
				...settings?.fetch
			},
			module: {
				...DefaultBrowserSettings.module,
				...settings?.module
			},
			device: {
				...DefaultBrowserSettings.device,
				...settings?.device
			},
			debug: {
				...DefaultBrowserSettings.debug,
				...settings?.debug
			},
			viewport: {
				...DefaultBrowserSettings.viewport,
				...settings?.viewport
			},
			canvasAdapter: settings?.canvasAdapter ?? DefaultBrowserSettings.canvasAdapter
		};
	}

	/**
	 * Validates settings.
	 *
	 * @param target Target.
	 * @param source Source.
	 * @param [parentNamespace] Parent namespace.
	 */
	private static validate(target: any, source: any, parentNamespace?: string): void {
		for (const key of Object.keys(source)) {
			if (target[key] === undefined) {
				const namespace = parentNamespace ? parentNamespace + '.' + key : key;
				throw new Error(`Unknown browser setting "${namespace}"`);
			}
			if (typeof target[key] === 'object' && !Array.isArray(target[key]) && target[key] !== null) {
				const namespace = parentNamespace ? parentNamespace + '.' + key : key;
				if (typeof source[key] !== 'object' || Array.isArray(source[key]) || source[key] === null) {
					throw new Error(`Browser setting "${namespace}" cannot be null`);
				}
				this.validate(target[key], source[key], namespace);
			} else {
				if (
					(typeof target[key] === 'boolean' ||
						typeof target[key] === 'number' ||
						typeof target[key] === 'string') &&
					typeof source[key] !== typeof target[key]
				) {
					const isValidPreventTimerLoops =
						key === 'preventTimerLoops' && typeof source[key] === 'object' && source[key] !== null;
					if (!isValidPreventTimerLoops) {
						const namespace = parentNamespace ? parentNamespace + '.' + key : key;
						throw new Error(
							`Browser setting "${namespace}" must be of type "${typeof target[key]}"`
						);
					}
				}
			}
		}
	}
}
