/**
 * 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 { assembleMethodSymbol } from "../../dom/customelement.mjs";
import {
	resolveClippingBoundaryElement,
	resolveParentPopperContentBoundary,
} from "./util/floating-ui.mjs";
import { Popper } from "../layout/popper.mjs";

export { ContextBase };

/**
 * @private
 * @type {symbol}
 */
const slotElementSymbol = Symbol("slotElement");

/**
 * @private
 * @type {symbol}
 */
const contentObserverSymbol = Symbol("contentObserver");

/**
 * @private
 * @type {symbol}
 */
const slotChangeHandlerSymbol = Symbol("slotChangeHandler");

/**
 * @private
 * @type {symbol}
 */
const iconElementSymbol = Symbol("iconElement");

/**
 * Base class for context popper controls that depend on slot content.
 */
class ContextBase extends Popper {
	/**
	 * @return {Object}
	 */
	get defaults() {
		return Object.assign({}, super.defaults, {
			popper: Object.assign({}, super.defaults.popper, {
				contentOverflow: "visible",
			}),
			features: Object.assign({}, super.defaults.features, {
				showIconWithoutContent: false,
			}),
		});
	}

	/**
	 * @private
	 * @return {void}
	 */
	[assembleMethodSymbol]() {
		super[assembleMethodSymbol]();
		initContentObserver.call(this);
		updateContentState.call(this);
	}

	/**
	 * @return {void}
	 */
	disconnectedCallback() {
		super.disconnectedCallback();
		disconnectContentObserver.call(this);
	}

	/**
	 * Context poppers should escape ancestor clipping containers when possible.
	 *
	 * @return {object}
	 */
	resolvePopperOptions() {
		const options = super.resolvePopperOptions();
		const { controlElement, popperElement } = getPopperElements.call(this);
		const parentPopperBoundary = resolveParentPopperContentBoundary(
			controlElement,
			popperElement,
		);

		if (
			resolveClippingBoundaryElement(controlElement, popperElement) ||
			parentPopperBoundary
		) {
			options.strategy = "fixed";
		}

		return options;
	}

	/**
	 * Nested context poppers inside another popper need bounded height.
	 *
	 * @return {string}
	 */
	resolveContentOverflowMode() {
		const configuredMode = super.resolveContentOverflowMode();
		if (configuredMode !== "visible") {
			return configuredMode;
		}

		const { controlElement, popperElement } = getPopperElements.call(this);
		if (resolveParentPopperContentBoundary(controlElement, popperElement)) {
			return "both";
		}

		return configuredMode;
	}
}

/**
 * @private
 * @return {void}
 */
function initContentObserver() {
	this[slotElementSymbol] = this.shadowRoot?.querySelector("slot") || null;
	this[iconElementSymbol] =
		this.shadowRoot?.querySelector("[data-monster-role=button] svg") || null;
	this[slotChangeHandlerSymbol] = () => updateContentState.call(this);

	if (this[slotElementSymbol]) {
		this[slotElementSymbol].addEventListener(
			"slotchange",
			this[slotChangeHandlerSymbol],
		);
	}

	this[contentObserverSymbol] = new MutationObserver(() => {
		updateContentState.call(this);
	});
	this[contentObserverSymbol].observe(this, {
		childList: true,
		subtree: true,
		characterData: true,
		attributes: true,
	});
}

function getPopperElements() {
	return {
		controlElement:
			this.shadowRoot?.querySelector('[data-monster-role="control"]') || null,
		popperElement:
			this.shadowRoot?.querySelector('[data-monster-role="popper"]') || null,
	};
}

/**
 * @private
 * @return {void}
 */
function disconnectContentObserver() {
	if (this[slotElementSymbol] && this[slotChangeHandlerSymbol]) {
		this[slotElementSymbol].removeEventListener(
			"slotchange",
			this[slotChangeHandlerSymbol],
		);
	}

	if (this[contentObserverSymbol] instanceof MutationObserver) {
		this[contentObserverSymbol].disconnect();
	}
}

/**
 * @private
 * @return {boolean}
 */
function hasContent() {
	for (const node of Array.from(this.childNodes)) {
		if (node.nodeType === Node.ELEMENT_NODE) {
			return true;
		}

		if (node.nodeType === Node.TEXT_NODE && node.textContent?.trim()) {
			return true;
		}
	}

	return false;
}

/**
 * @private
 * @return {void}
 */
function updateContentState() {
	const contentAvailable = hasContent.call(this);
	const showIconWithoutContent =
		this.getOption("features.showIconWithoutContent", false) === true;

	this.setOption("disabled", !contentAvailable);

	if (!contentAvailable) {
		this.hideDialog();
	}

	if (this[iconElementSymbol]) {
		const showIcon = contentAvailable || showIconWithoutContent;
		this[iconElementSymbol].classList.toggle("hidden", !showIcon);
	}
}
