/**
 * 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 { instanceSymbol } from "../../constants.mjs";
import {
	assembleMethodSymbol,
	CustomElement,
	registerCustomElement,
} from "../../dom/customelement.mjs";
import { findTargetElementFromEvent } from "../../dom/events.mjs";
import { clone } from "../../util/clone.mjs";
import { ColumnBarStyleSheet } from "./stylesheet/column-bar.mjs";
import { getLocaleOfDocument } from "../../dom/locale.mjs";
import { hasObjectLink } from "../../dom/attributes.mjs";
import { customElementUpdaterLinkSymbol } from "../../dom/constants.mjs";
import { getGlobalObject } from "../../types/global.mjs";
import { UniqueQueue } from "../../types/uniquequeue.mjs";
import {
	createFloatingPopper,
	popperInstanceSymbol,
} from "../form/util/popper.mjs";

export { ColumnBar };

/**
 * @private
 * @type {symbol}
 */
const settingsButtonElementSymbol = Symbol("settingButtonElement");

/**
 * @private
 * @type {symbol}
 */
const settingsButtonEventHandlerSymbol = Symbol("settingsButtonEventHandler");

/**
 * @private
 * @type {symbol}
 */
const settingsLayerElementSymbol = Symbol("settingsLayerElement");

/**
 * @private
 * @type {symbol}
 */
const dotsContainerElementSymbol = Symbol("dotsContainerElement");

/**
 * @private
 * @type {symbol}
 */
const closeEventHandlerSymbol = Symbol("closeEventHandler");

/**
 * @private
 * @type {symbol}
 */
const resizeObserverSymbol = Symbol("resizeObserver");

/**
 * @private
 * @type {symbol}
 */
const dotsMutationObserverSymbol = Symbol("dotsMutationObserver");
/**
 * @private
 * @type {symbol}
 */
const dotsUpdateInProgressSymbol = Symbol("dotsUpdateInProgress");
/**
 * @private
 * @type {symbol}
 */
const dotsFrameRequestSymbol = Symbol("dotsFrameRequest");
/**
 * @private
 * @type {symbol}
 */
const dotsLayoutCacheSymbol = Symbol("dotsLayoutCache");

/**
 * @private
 * @type {UniqueQueue}
 */
const scheduledDotsUpdates = new UniqueQueue();

/**
 * @private
 * @type {number|null}
 */
let scheduledDotsFrameRequest = null;

/**
 * @private
 * @type {CSSStyleSheet}
 */
const ResponsiveColumnBarStyleSheet = new CSSStyleSheet();

ResponsiveColumnBarStyleSheet.replaceSync(`
:host(:not(.small)) [data-monster-role=control] {
	align-items: center;
	flex-direction: row;
	gap: 0;
}

:host(:not(.small)) [data-monster-role=dots] {
	margin: 0 15px 0 0;
}

:host(.small) [data-monster-role=control] {
	align-items: flex-end;
	flex-direction: column;
	gap: 0.5rem;
}

:host(.small) [data-monster-role=dots] {
	margin: 0;
}
`);

/**
 * A column bar for a datatable
 *
 * @fragments /fragments/components/datatable/datatable/
 *
 * @example /examples/components/datatable/empty
 *
 * @copyright Volker Schukai
 * @summary The ColumnBar component is used to show and configure the columns of a datatable.
 **/
class ColumnBar extends CustomElement {
	/**
	 * This method is called by the `instanceof` operator.
	 * @return {symbol}
	 */
	static get [instanceSymbol]() {
		return Symbol.for("@schukai/monster/components/column-bar@@instance");
	}

	/**
	 * This method is called to customize the component.
	 * @returns {Map<unknown, unknown>}
	 */
	get customization() {
		return new Map([...super.customization, ["templateFormatter.i18n", true]]);
	}

	/**
	 * To set the options via the HTML tag, the attribute `data-monster-options` must be used.
	 * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control}
	 *
	 * The individual configuration values can be found in the table.
	 *
	 * @property {Object} templates Template definitions
	 * @property {string} templates.main Main template
	 * @property {object} labels Locale definitions
	 * @property {string} locale.settings The text for the settings button
	 * @property {object} dots Dots configuration
	 * @property {number} dots.maxVisible Max dots to show (0 disables limit)
	 */
	get defaults() {
		return Object.assign({}, super.defaults, {
			templates: {
				main: getTemplate(),
			},
			labels: getTranslations(),
			dots: {
				maxVisible: 15,
			},

			columns: [],
		});
	}

	/**
	 * Called every time the element is added to the DOM. Useful for running initialization code.
	 * @return {void}
	 * @since 4.14.0
	 */
	connectedCallback() {
		super.connectedCallback();

		this[closeEventHandlerSymbol] = (event) => {
			const path = event.composedPath();
			const isOutsideElement = !path.includes(this);
			const isOutsideShadow = !path.includes(this.shadowRoot);

			if (
				isOutsideElement &&
				isOutsideShadow &&
				this[settingsLayerElementSymbol]
			) {
				this[settingsLayerElementSymbol].classList.remove("visible");
			}
		};

		getGlobalObject("document").addEventListener(
			"click",
			this[closeEventHandlerSymbol],
		);
		getGlobalObject("document").addEventListener(
			"touch",
			this[closeEventHandlerSymbol],
		);

		queueMicrotask(() => {
			initDotsObservers.call(this);
			scheduleDotsVisibilityUpdate.call(this);
		});
	}

	/**
	 * Called every time the element is removed from the DOM. Useful for running clean up code.
	 *
	 * @return {void}
	 * @since 4.14.0
	 */
	disconnectedCallback() {
		super.disconnectedCallback();

		if (this[resizeObserverSymbol] instanceof ResizeObserver) {
			this[resizeObserverSymbol].disconnect();
			this[resizeObserverSymbol] = null;
		}

		if (this[dotsMutationObserverSymbol] instanceof MutationObserver) {
			this[dotsMutationObserverSymbol].disconnect();
			this[dotsMutationObserverSymbol] = null;
		}

		removeScheduledDotsUpdate(this);
		this[dotsFrameRequestSymbol] = null;

		if (this[closeEventHandlerSymbol]) {
			getGlobalObject("document").removeEventListener(
				"click",
				this[closeEventHandlerSymbol],
			);
			getGlobalObject("document").removeEventListener(
				"touch",
				this[closeEventHandlerSymbol],
			);
			this[closeEventHandlerSymbol] = null;
		}

		this[popperInstanceSymbol]?.destroy();
	}

	/**
	 * @return {string}
	 */
	static getTag() {
		return "monster-column-bar";
	}

	/**
	 *
	 * @return {void}
	 */
	[assembleMethodSymbol]() {
		super[assembleMethodSymbol]();

		initControlReferences.call(this);
		initEventHandler.call(this);
	}

	/**
	 * @return {CSSStyleSheet[]}
	 */
	static getCSSStyleSheet() {
		return [ColumnBarStyleSheet, ResponsiveColumnBarStyleSheet];
	}
}

/**
 * @private
 * @returns {{settings: string}}
 */
function getTranslations() {
	const locale = getLocaleOfDocument();
	switch (locale.language) {
		case "de": // German
			return { settings: "Einstellungen" };
		case "fr": // French
			return { settings: "Paramètres" };
		case "es": // Spanish
			return { settings: "Configuración" };
		case "zh": // Mandarin (Chinese)
			return { settings: "设置" };
		case "hi": // Hindi
			return { settings: "सेटिंग्स" };
		case "bn": // Bengali
			return { settings: "সেটিংস" };
		case "pt": // Portuguese
			return { settings: "Configurações" };
		case "ru": // Russian
			return { settings: "Настройки" };
		case "ja": // Japanese
			return { settings: "設定" };
		case "pa": // Western Punjabi
			return { settings: "ਸੈਟਿੰਗਾਂ" };
		case "mr": // Marathi
			return { settings: "सेटिंग्ज" };
		case "it": // Italian
			return { settings: "Impostazioni" };
		case "nl": // Dutch
			return { settings: "Instellingen" };
		case "sv": // Swedish
			return { settings: "Inställningar" };
		case "pl": // Polish
			return { settings: "Ustawienia" };
		case "da": // Danish
			return { settings: "Indstillinger" };
		case "fi": // Finnish
			return { settings: "Asetukset" };
		case "no": // Norwegian
			return { settings: "Innstillinger" };
		case "cs": // Czech
			return { settings: "Nastavení" };
		default: // English fallback
		case "en":
			return { settings: "Settings" };
	}
}

/**
 * @private
 * @return {ColumnBar}
 */
function initControlReferences() {
	if (!this.shadowRoot) {
		throw new Error("no shadow-root is defined");
	}

	this[settingsButtonElementSymbol] = this.shadowRoot.querySelector(
		"[data-monster-role=settings-button]",
	);

	this[settingsLayerElementSymbol] = this.shadowRoot.querySelector(
		"[data-monster-role=settings-layer]",
	);

	this[dotsContainerElementSymbol] = this.shadowRoot.querySelector(
		"[data-monster-role=dots]",
	);

	return this;
}

/**
 * @private
 */
function initEventHandler() {
	const self = this;

	self[popperInstanceSymbol] = createFloatingPopper(
		self[settingsButtonElementSymbol],
		self[settingsLayerElementSymbol],
		{
			placement: "auto",

			modifiers: [
				{
					name: "offset",
					options: {
						offset: [10, 10],
					},
				},
			],
		},
	);

	self[dotsContainerElementSymbol].addEventListener("click", function (event) {
		const element = findTargetElementFromEvent(
			event,
			"data-monster-role",
			"column",
		);
		if (element) {
			const index = element.getAttribute("data-monster-index");
			event.preventDefault();

			const columns = clone(self.getOption("columns"));

			const column = columns.find((col) => {
				return parseInt(col.index) === parseInt(index);
			});

			column.visible = !column.visible;
			self.setOption("columns", columns);
		}
	});

	self[settingsButtonEventHandlerSymbol] = (event) => {
		const clickTarget = event.composedPath()?.[0];
		if (
			self[settingsLayerElementSymbol] === clickTarget ||
			self[settingsLayerElementSymbol].contains(clickTarget)
		) {
			return;
		}

		document.body.removeEventListener(
			"click",
			self[settingsButtonEventHandlerSymbol],
		);
	};

	self[settingsButtonElementSymbol].addEventListener("click", function (event) {
		const element = findTargetElementFromEvent(
			event,
			"data-monster-role",
			"settings-button",
		);

		if (element) {
			self[settingsLayerElementSymbol].classList.toggle("visible");
			event.preventDefault();

			if (self[settingsLayerElementSymbol].classList.contains("visible")) {
				self[popperInstanceSymbol].update();

				queueMicrotask(() => {
					document.body.addEventListener(
						"click",
						self[settingsButtonEventHandlerSymbol],
					);
				});
			}
		}
	});

	self[settingsLayerElementSymbol].addEventListener("change", function (event) {
		const control = event.target;
		const index = control.getAttribute("data-monster-index");

		const columns = clone(self.getOption("columns"));

		const column = columns.find((col) => {
			return parseInt(col.index) === parseInt(index);
		});

		column.visible = control.checked;
		self.setOption("columns", columns);
	});
}

/**
 * @private
 */
function initDotsObservers() {
	const controlElement = this.shadowRoot?.querySelector(
		"[data-monster-role=control]",
	);
	const parentElement = this.parentElement;

	if (controlElement && !this[resizeObserverSymbol]) {
		this[resizeObserverSymbol] = new ResizeObserver((entries) => {
			updateDotsLayoutResizeCache.call(this, entries, {
				controlElement,
				parentElement,
			});
			scheduleDotsVisibilityUpdate.call(this);
		});
		this[resizeObserverSymbol].observe(controlElement);
		if (parentElement) {
			this[resizeObserverSymbol].observe(parentElement);
		}
	}

	if (this[dotsContainerElementSymbol] && !this[dotsMutationObserverSymbol]) {
		this[dotsMutationObserverSymbol] = new MutationObserver((mutations) => {
			if (
				this[dotsUpdateInProgressSymbol] ||
				mutations.every(isOverflowIndicatorMutation)
			) {
				return;
			}
			invalidateDotsLayoutCache.call(this, {
				dots: true,
				visibility: true,
			});
			scheduleDotsVisibilityUpdate.call(this);
		});
		this[dotsMutationObserverSymbol].observe(this[dotsContainerElementSymbol], {
			childList: true,
		});
	}
}

/**
 * @private
 */
function scheduleDotsVisibilityUpdate() {
	scheduledDotsUpdates.add(this);
	this[dotsFrameRequestSymbol] = true;

	if (scheduledDotsFrameRequest !== null) return;

	scheduledDotsFrameRequest = requestAnimationFrame(() => {
		const pending = [];
		while (!scheduledDotsUpdates.isEmpty()) {
			pending.push(scheduledDotsUpdates.poll());
		}
		scheduledDotsFrameRequest = null;

		const layouts = [];
		for (const element of pending) {
			element[dotsFrameRequestSymbol] = null;
			const layout = measureDotsLayout.call(element);
			if (layout) {
				layouts.push(layout);
			}
		}

		for (const layout of layouts) {
			applyDotsLayout(layout);
		}
	});
}

/**
 * @private
 */
function measureDotsLayout() {
	if (this[dotsUpdateInProgressSymbol]) {
		return null;
	}

	if (!this.shadowRoot || !this[dotsContainerElementSymbol]) {
		return null;
	}

	const controlElement = this.shadowRoot.querySelector(
		"[data-monster-role=control]",
	);
	const settingsButton = this[settingsButtonElementSymbol];
	const parentElement = this.parentElement;

	if (!controlElement || !settingsButton) {
		return null;
	}

	const dotsContainer = this[dotsContainerElementSymbol];
	const cache = getDotsLayoutCache.call(this);
	const dots = Array.from(
		dotsContainer.querySelectorAll("li:not(.dots-overflow-indicator)"),
	);

	if (dots.length === 0) {
		return {
			hostElement: this,
			dots,
			dotsContainer,
			hiddenCount: 0,
			hideContainer: true,
			visibleDots: 0,
		};
	}

	if (cache.dotsCount !== dots.length) {
		cache.dotsCount = dots.length;
		cache.dotSlotWidth = null;
		cache.lastVisibleDots = null;
		cache.lastHiddenCount = null;
	}

	if (cache.controlWidth === null || cache.controlWidth <= 0) {
		cache.controlWidth = controlElement.getBoundingClientRect().width;
	}
	if (cache.settingsWidth === null || cache.settingsWidth <= 0) {
		cache.settingsWidth = settingsButton.getBoundingClientRect().width;
	}

	const availableWidth = Math.max(
		0,
		getAvailableWidth(parentElement, cache.settingsWidth, this, cache) ??
			cache.controlWidth - cache.settingsWidth - 12,
	);

	if (cache.dotSlotWidth === null || cache.dotSlotWidth <= 0) {
		cache.dotSlotWidth = getDotSlotWidth(dots[0]);
	}

	const maxDots =
		cache.dotSlotWidth > 0
			? Math.floor(availableWidth / cache.dotSlotWidth)
			: dots.length;
	const configuredMaxVisible = parseInt(this.getOption("dots.maxVisible"), 10);
	const enforceMaxVisible =
		Number.isFinite(configuredMaxVisible) && configuredMaxVisible > 0;

	if (maxDots <= 1) {
		return {
			hostElement: this,
			dots,
			dotsContainer,
			hiddenCount: 0,
			hideContainer: true,
			visibleDots: 0,
		};
	}

	const configLimit = enforceMaxVisible
		? configuredMaxVisible
		: Number.POSITIVE_INFINITY;
	const baseLimit = Math.min(maxDots, configLimit, dots.length);
	const needsIndicator = baseLimit < dots.length;

	let visibleDots = baseLimit;
	if (needsIndicator) {
		visibleDots = Math.min(Math.max(1, maxDots - 1), configLimit, dots.length);
	}

	return {
		hostElement: this,
		dots,
		dotsContainer,
		hiddenCount: dots.length - visibleDots,
		hideContainer: false,
		visibleDots,
	};
}

/**
 * @private
 * @param {object} layout
 * @return {void}
 */
function applyDotsLayout(layout) {
	const cache = getDotsLayoutCache.call(layout.hostElement);

	layout.hostElement[dotsUpdateInProgressSymbol] = true;
	try {
		if (cache.lastContainerHidden !== layout.hideContainer) {
			layout.dotsContainer.classList.toggle(
				"dots-hidden",
				layout.hideContainer,
			);
			cache.lastContainerHidden = layout.hideContainer;
		}

		if (
			cache.lastVisibleDots !== layout.visibleDots ||
			cache.lastDotsCount !== layout.dots.length
		) {
			for (let i = 0; i < layout.dots.length; i++) {
				const shouldHide = i >= layout.visibleDots;
				if (
					layout.dots[i].classList.contains("dots-overflow-hidden") !==
					shouldHide
				) {
					layout.dots[i].classList.toggle("dots-overflow-hidden", shouldHide);
				}
			}
			cache.lastVisibleDots = layout.visibleDots;
			cache.lastDotsCount = layout.dots.length;
		}

		if (cache.lastHiddenCount !== layout.hiddenCount) {
			updateOverflowIndicator(layout.dotsContainer, layout.hiddenCount);
			cache.lastHiddenCount = layout.hiddenCount;
		}
	} finally {
		layout.hostElement[dotsUpdateInProgressSymbol] = false;
	}
}

/**
 * @private
 * @param {MutationRecord} mutation
 * @return {boolean}
 */
function isOverflowIndicatorMutation(mutation) {
	const changedNodes = Array.from(mutation.addedNodes).concat(
		Array.from(mutation.removedNodes),
	);

	return (
		changedNodes.length > 0 &&
		changedNodes.every((node) => {
			return (
				node instanceof HTMLElement &&
				node.classList.contains("dots-overflow-indicator")
			);
		})
	);
}

/**
 * @private
 * @param {HTMLElement} dotsContainer
 * @param {number} hiddenCount
 * @return {void}
 */
function updateOverflowIndicator(dotsContainer, hiddenCount) {
	let overflowIndicator = dotsContainer.querySelector(
		".dots-overflow-indicator",
	);

	if (hiddenCount <= 0) {
		overflowIndicator?.remove();
		return;
	}

	const label = `+${hiddenCount}`;
	if (!overflowIndicator) {
		overflowIndicator = document.createElement("li");
		overflowIndicator.className = "dots-overflow-indicator";
		dotsContainer.appendChild(overflowIndicator);
	}

	if (overflowIndicator.textContent !== label) {
		overflowIndicator.textContent = label;
	}
}

/**
 * @private
 * @param {ColumnBar} element
 * @return {void}
 */
function removeScheduledDotsUpdate(element) {
	const pending = [];
	while (!scheduledDotsUpdates.isEmpty()) {
		const item = scheduledDotsUpdates.poll();
		if (item !== element) {
			pending.push(item);
		}
	}
	for (const item of pending) {
		scheduledDotsUpdates.add(item);
	}
}

/**
 * @private
 * @return {object}
 */
function getDotsLayoutCache() {
	if (!this[dotsLayoutCacheSymbol]) {
		this[dotsLayoutCacheSymbol] = {
			controlWidth: null,
			dotSlotWidth: null,
			dotsCount: null,
			lastContainerHidden: null,
			lastDotsCount: null,
			lastHiddenCount: null,
			lastVisibleDots: null,
			parentGap: null,
			parentWidth: null,
			settingsWidth: null,
			siblingsSignature: null,
			siblingsWidth: null,
		};
	}

	return this[dotsLayoutCacheSymbol];
}

/**
 * @private
 * @param {object} options
 * @return {void}
 */
function invalidateDotsLayoutCache(options = {}) {
	const cache = getDotsLayoutCache.call(this);

	if (options.dots === true) {
		cache.dotSlotWidth = null;
		cache.dotsCount = null;
	}

	if (options.visibility === true) {
		cache.lastContainerHidden = null;
		cache.lastDotsCount = null;
		cache.lastHiddenCount = null;
		cache.lastVisibleDots = null;
	}

	if (options.control === true) {
		cache.controlWidth = null;
	}

	if (options.parent === true) {
		cache.parentWidth = null;
	}

	if (options.settings === true) {
		cache.settingsWidth = null;
	}

	if (options.siblings === true) {
		cache.parentGap = null;
		cache.siblingsSignature = null;
		cache.siblingsWidth = null;
	}
}

/**
 * @private
 * @param {ResizeObserverEntry[]} entries
 * @param {object} elements
 * @return {void}
 */
function updateDotsLayoutResizeCache(
	entries,
	{ controlElement, parentElement },
) {
	const cache = getDotsLayoutCache.call(this);

	if (!Array.isArray(entries) || entries.length === 0) {
		invalidateDotsLayoutCache.call(this, {
			control: true,
			parent: true,
			siblings: true,
		});
		return;
	}

	for (const entry of entries) {
		const width = getResizeEntryWidth(entry);
		if (width === null) continue;

		if (entry.target === controlElement) {
			cache.controlWidth = width;
		}
		if (entry.target === parentElement) {
			cache.parentWidth = width;
			cache.siblingsSignature = null;
			cache.siblingsWidth = null;
		}
	}
}

/**
 * @private
 * @param {ResizeObserverEntry} entry
 * @return {number|null}
 */
function getResizeEntryWidth(entry) {
	const borderSize = Array.isArray(entry.borderBoxSize)
		? entry.borderBoxSize[0]
		: entry.borderBoxSize;
	const contentSize = Array.isArray(entry.contentBoxSize)
		? entry.contentBoxSize[0]
		: entry.contentBoxSize;
	const width =
		borderSize?.inlineSize ??
		contentSize?.inlineSize ??
		entry.contentRect?.width;

	return Number.isFinite(width) ? width : null;
}

/**
 * @private
 * @param {HTMLElement} dot
 * @return {number}
 */
function getDotSlotWidth(dot) {
	if (!dot) return 0;
	const styles = getComputedStyle(dot);
	const marginRight = parseFloat(styles.marginRight || "0");
	const marginLeft = parseFloat(styles.marginLeft || "0");
	return dot.getBoundingClientRect().width + marginLeft + marginRight;
}

/**
 * @private
 * @param {HTMLElement|null} parent
 * @param {number} settingsWidth
 * @param {ColumnBar} hostElement
 * @param {object} cache
 * @return {number|null}
 */
function getAvailableWidth(parent, settingsWidth, hostElement, cache) {
	if (!parent) return null;

	const parentWidth =
		cache.parentWidth ?? parent.getBoundingClientRect().width ?? null;
	if (!parentWidth) return null;
	cache.parentWidth = parentWidth;

	const siblings = Array.from(parent.children).filter(
		(el) => el !== hostElement,
	);
	const siblingsSignature = siblings
		.map(
			(sibling) => sibling.getAttribute("data-monster-role") || sibling.tagName,
		)
		.join("|");

	if (
		cache.siblingsSignature !== siblingsSignature ||
		cache.siblingsWidth === null ||
		cache.parentGap === null
	) {
		const styles = getComputedStyle(parent);
		const gapValue = styles.columnGap || styles.gap || "0";
		cache.parentGap = parseFloat(gapValue) || 0;
		cache.siblingsSignature = siblingsSignature;
		cache.siblingsWidth = 0;

		for (const sibling of siblings) {
			const siblingStyles = getComputedStyle(sibling);
			if (siblingStyles.display === "none") {
				continue;
			}
			cache.siblingsWidth += sibling.getBoundingClientRect().width;
		}
	}

	const gaps = siblings.length > 0 ? siblings.length : 0;
	return Math.max(
		0,
		parentWidth - cache.siblingsWidth - cache.parentGap * gaps - settingsWidth,
	);
}

/**
 * @private
 * @return {string}
 */
function getTemplate() {
	// language=HTML
	return `
        <template id="column">
            <div data-monster-role="column">
                <label><input type="checkbox" data-monster-attributes="
                data-monster-index path:column.index,
                checked path:column.visible | ?:checked:"><span
                data-monster-replace="path:column.name"
                ></span></label>
            </div>
        </template>
        <template id="dots">
            <li data-monster-insert="">
                <a href="#" data-monster-role="column"
                   data-monster-attributes="
                   class path:dots.visible | ?:is-hidden:is-visible,
                   title path:dots.name,
                     data-monster-index path:dots.index">
                </a>
            </li>
        </template>

        <div data-monster-role="control" part="control" data-monster-select-this="true" data-monster-attributes="class path:columns | has-entries | ?::hidden">
            <ul data-monster-insert="dots path:columns" 
                data-monster-role="dots"></ul>
            <a href="#" data-monster-role="settings-button">i18n{settings}</a>
            <div data-monster-role="settings-layer">
                <div data-monster-insert="column path:columns" data-monster-role="settings-popup-list">
                </div>
            </div>
		</div>
    `;
}

registerCustomElement(ColumnBar);
