/**
 * Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved.
 * Node module: @schukai/monster
 *
 * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
 * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
 *
 * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
 * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
 * For more information about purchasing a commercial license, please contact Volker Schukai.
 *
 * SPDX-License-Identifier: AGPL-3.0
 */

import { isString } from "../types/is.mjs";
import { validateString } from "../types/validate.mjs";

export { getSlottedElements, getSlottedNodes };

/**
 * @private
 * @param {String|undefined} query
 * @param {String|undefined|null} name name of the slot (if the parameter is undefined, all slots are searched, if the parameter has the value null, all slots without a name are searched. if a string is specified, the slots with this name are searched.)
 * @return {*}
 * @this CustomElement
 * @license AGPLv3
 * @since 3.33.0
 * @throws {Error} query must be a string
 */
function getSlottedNodes(query, name) {
	const result = new Set();

	if (!this.shadowRoot) {
		return result;
	}

	let selector = "slot";
	if (name !== undefined) {
		if (name === null) {
			selector += ":not([name])";
		} else {
			selector += `[name=${validateString(name)}]`;
		}
	}

	const slots = this.shadowRoot.querySelectorAll(selector);

	for (const [, slot] of Object.entries(slots)) {
		slot.assignedNodes().forEach(function (node) {
			if (node === null || node === undefined) {
				return;
			}

			if (isString(query)) {
				node.querySelectorAll(query).forEach(function (n) {
					result.add(n);
				});

				if (node.matches(query)) {
					result.add(node);
				}
			} else if (query !== undefined) {
				throw new Error("query must be a string");
			} else {
				result.add(node);
			}
		});
	}

	return result;
}

/**
 * @private
 * @param {String|undefined} query
 * @param {String|undefined|null} name name of the slot (if the parameter is undefined, all slots are searched, if the parameter has the value null, all slots without a name are searched. if a string is specified, the slots with this name are searched.)
 * @return {*}
 * @this CustomElement
 * @license AGPLv3
 * @since 1.23.0
 * @throws {Error} query must be a string
 */
function getSlottedElements(query, name) {
	const result = new Set();

	if (!(this.shadowRoot instanceof ShadowRoot)) {
		return result;
	}

	let selector = "slot";
	if (name !== undefined) {
		if (name === null) {
			selector += ":not([name])";
		} else {
			selector += `[name=${validateString(name)}]`;
		}
	}

	const slots = this.shadowRoot.querySelectorAll(selector);

	for (const [, slot] of Object.entries(slots)) {
		slot.assignedElements().forEach(function (node) {
			if (
				!(node instanceof HTMLElement) &&
				!(node instanceof SVGElement) &&
				!(node instanceof MathMLElement)
			)
				return;

			if (isString(query)) {
				if (query.length > 0) {
					node.querySelectorAll(query).forEach(function (n) {
						result.add(n);
					});

					if (node.matches(query)) {
						result.add(node);
					}
				} else {
					result.add(node);
				}
			} else if (query !== undefined) {
				throw new Error("query must be a string and not empty");
			} else {
				result.add(node);
			}
		});
	}

	return result;
}
