import type {ReactiveController, ReactiveControllerHost} from 'lit';

/**
 * A reactive controller that determines when slots exist.
 *
 * Copied from Shoelace
 * /src/internal/slot.ts
 */
export class HasSlotController implements ReactiveController
{
	host : ReactiveControllerHost & Element;
	slotNames : string[] = [];

	constructor(host : ReactiveControllerHost & Element, ...slotNames : string[])
	{
		(this.host = host).addController(this);
		this.slotNames = slotNames;
	}

	private hasDefaultSlot()
	{
		return [...this.host.childNodes].some(node =>
		{
			if(node.nodeType === node.TEXT_NODE && node.textContent!.trim() !== '')
			{
				return true;
			}

			if(node.nodeType === node.ELEMENT_NODE)
			{
				const el = node as HTMLElement;
				const tagName = el.tagName.toLowerCase();

				// Ignore visually hidden elements since they aren't rendered
				if(tagName === 'sl-visually-hidden')
				{
					return false;
				}

				// If it doesn't have a slot attribute, it's part of the default slot
				if(!el.hasAttribute('slot'))
				{
					return true;
				}
			}

			return false;
		});
	}

	private hasNamedSlot(name : string)
	{
		return this.host.querySelector(`:scope > [slot="${name}"]`) !== null;
	}

	test(slotName : string)
	{
		return slotName === '[default]' ? this.hasDefaultSlot() : this.hasNamedSlot(slotName);
	}

	hostConnected()
	{
		this.host.shadowRoot!.addEventListener('slotchange', this.handleSlotChange);
	}

	hostDisconnected()
	{
		this.host.shadowRoot!.removeEventListener('slotchange', this.handleSlotChange);
	}

	private handleSlotChange = (event : Event) =>
	{
		const slot = event.target as HTMLSlotElement;

		if((this.slotNames.includes('[default]') && !slot.name) || (slot.name && this.slotNames.includes(slot.name)))
		{
			this.host.requestUpdate();
		}
	};
}

/**
 * Given a slot, this function iterates over all of its assigned element and text nodes and returns the concatenated
 * HTML as a string. This is useful because we can't use slot.innerHTML as an alternative.
 */
export function getInnerHTML(slot : HTMLSlotElement) : string
{
	const nodes = slot.assignedNodes({flatten: true});
	let html = '';

	[...nodes].forEach(node =>
	{
		if(node.nodeType === Node.ELEMENT_NODE)
		{
			html += (node as HTMLElement).outerHTML;
		}

		if(node.nodeType === Node.TEXT_NODE)
		{
			html += node.textContent;
		}
	});

	return html;
}

/**
 * Given a slot, this function iterates over all of its assigned text nodes and returns the concatenated text as a
 * string. This is useful because we can't use slot.textContent as an alternative.
 */
export function getTextContent(slot : HTMLSlotElement | undefined | null) : string
{
	if(!slot)
	{
		return '';
	}
	const nodes = slot.assignedNodes({flatten: true});
	let text = '';

	[...nodes].forEach(node =>
	{
		if(node.nodeType === Node.TEXT_NODE)
		{
			text += node.textContent;
		}
	});

	return text;
}
