import EventTarget from '../event/EventTarget.js';
import * as PropertySymbol from '../PropertySymbol.js';
import type Event from '../event/Event.js';
import type BrowserWindow from '../window/BrowserWindow.js';
import type { TEventListener } from '../event/TEventListener.js';
import MediaQueryListEvent from '../event/events/MediaQueryListEvent.js';
import type IMediaQueryItem from './MediaQueryItem.js';
import MediaQueryParser from './MediaQueryParser.js';

/**
 * Media Query List.
 *
 * Reference:
 * https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList.
 */
export default class MediaQueryList extends EventTarget {
	public onchange: ((event: Event) => void) | null = null;
	#window: BrowserWindow;
	#items: IMediaQueryItem[] | null = null;
	#media: string;
	#rootFontSize: string | number | null = null;

	/**
	 * Constructor.
	 *
	 * @param options Options.
	 * @param options.window Owner window.
	 * @param options.media Media.
	 * @param [options.rootFontSize] Root font size.
	 */
	constructor(options: {
		window: BrowserWindow;
		media: string;
		rootFontSize?: string | number | null;
	}) {
		super();
		this.#window = options.window;
		this.#media = options.media;
		this.#rootFontSize = options.rootFontSize || null;
	}

	/**
	 * Returns media.
	 *
	 * @returns Media.
	 */
	public get media(): string {
		this.#items =
			this.#items ||
			MediaQueryParser.parse({
				window: this.#window,
				mediaQuery: this.#media,
				rootFontSize: this.#rootFontSize
			});

		return this.#items.map((item) => item.toString()).join(', ');
	}

	/**
	 * Returns "true" if the document matches.
	 *
	 * @returns Matches.
	 */
	public get matches(): boolean {
		this.#items =
			this.#items ||
			MediaQueryParser.parse({
				window: this.#window,
				mediaQuery: this.#media,
				rootFontSize: this.#rootFontSize
			});

		for (const item of this.#items) {
			if (!item.matches()) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Adds a listener.
	 *
	 * @deprecated
	 * @param callback Callback.
	 */
	public addListener(callback: (event: Event) => void): void {
		this.addEventListener('change', callback);
	}

	/**
	 * Removes listener.
	 *
	 * @deprecated
	 * @param callback Callback.
	 */
	public removeListener(callback: (event: Event) => void): void {
		this.removeEventListener('change', callback);
	}

	/**
	 * @override
	 */
	public addEventListener(type: string, listener: TEventListener): void {
		super.addEventListener(type, listener);
		if (type === 'change') {
			let matchesState = false;
			const resizeListener = (): void => {
				const matches = this.matches;
				if (matches !== matchesState) {
					matchesState = matches;
					this.dispatchEvent(new MediaQueryListEvent('change', { matches, media: this.media }));
				}
			};
			(<any>listener)[PropertySymbol.windowResizeListener] = resizeListener;
			this.#window.addEventListener('resize', resizeListener);
		}
	}

	/**
	 * @override
	 */
	public removeEventListener(type: string, listener: TEventListener): void {
		super.removeEventListener(type, listener);
		if (type === 'change' && (<any>listener)[PropertySymbol.windowResizeListener]) {
			this.#window.removeEventListener(
				'resize',
				(<any>listener)[PropertySymbol.windowResizeListener]
			);
		}
	}
}
