import {
	parseBracketedKeyValueHash,
	createBracketedKeyValueHash,
} from "../../../text/bracketed-key-value-hash.mjs";

/**
 * Synchronizes a <monster-tabs> instance with the URL hash,
 * including active tab and all existing tab IDs.
 *
 * @param {HTMLElement} tabsEl   - The monster-tabs element
 * @param {string} selector      - Hash selector name (e.g. "tabs", "tabs2")
 * @param {string} activeKey     - Key for the active tab (default: "active")
 * @param {string} allTabsKey    - Key for all tab IDs (default: "all")
 */
export function attachTabsHashSync(
	tabsEl,
	selector = "tabs",
	activeKey = "active",
	allTabsKey = "all",
) {
	if (!(tabsEl instanceof HTMLElement)) {
		throw new TypeError("Expected a monster-tabs HTMLElement");
	}

	let lastKnownActiveTabId = null;
	let lastKnownAllTabIds = [];

	function getTabKey(tab) {
		return tab.getAttribute("data-monster-name") || tab.getAttribute("id");
	}

	/**
	 * Reads active and all tab IDs from the URL hash.
	 * @returns {{activeTabId: string|null, allTabIds: string[]}}
	 */
	function getTabStateFromHash() {
		const hashObj = parseBracketedKeyValueHash(location.hash);
		const tabsData = hashObj?.[selector] ?? {};
		const activeTabId = tabsData[activeKey] ?? null;
		const allTabIdsString = tabsData[allTabsKey] ?? "";
		const allTabIds = allTabIdsString
			.split(",")
			.filter((id) => id.trim() !== "");
		return { activeTabId, allTabIds };
	}

	/**
	 * Synchronizes tab state from hash on page load and hash changes.
	 */
	function syncFromHash() {
		const { activeTabId, allTabIds } = getTabStateFromHash();

		// Sync active tab
		if (activeTabId && activeTabId !== lastKnownActiveTabId) {
			tabsEl.activeTab(activeTabId);
			lastKnownActiveTabId = activeTabId;
		}

		// Sync all tabs (add/remove tabs based on hash)
		const currentTabs = tabsEl.getTabs().map(getTabKey);

		// Add tabs that are in hash but not in DOM
		for (const tabId of allTabIds) {
			if (!currentTabs.includes(tabId)) {
				// You'll need a way to get the label and content for new tabs
				// This is a placeholder. You might fetch it or have a default.
				// For existing tabs in the HTML, we're just ensuring they are present.
				// For truly new tabs from the hash, you'd need their content/label.
				// For this example, we'll assume the initial HTML already has all potential tabs,
				// and we're just making sure they are displayed if their IDs are in the hash.
				// If you truly want to create new tabs from scratch based on the hash,
				// you'd need more information in the hash or a lookup mechanism.
				console.warn(
					`Tab with ID '${tabId}' found in hash but not in DOM. Add logic to create it if necessary.`,
				);
			}
		}

		// Remove tabs that are in DOM but not in hash
		for (const tabId of currentTabs) {
			if (!allTabIds.includes(tabId)) {
				tabsEl.removeTab(tabId);
			}
		}

		lastKnownAllTabIds = allTabIds;
	}

	window.addEventListener("hashchange", syncFromHash);
	syncFromHash(); // initial load

	/**
	 * Writes the current active tab and all tab IDs to the URL hash.
	 * @param {string|null} activeTabId - The ID of the currently active tab.
	 * @param {string[]} allTabIds - An array of all current tab IDs.
	 */
	function writeHash(activeTabId, allTabIds) {
		const hashObj = parseBracketedKeyValueHash(location.hash);
		hashObj[selector] = { ...(hashObj[selector] ?? {}) };

		if (activeTabId) {
			hashObj[selector][activeKey] = activeTabId;
		} else {
			delete hashObj[selector][activeKey];
		}

		if (allTabIds.length > 0) {
			hashObj[selector][allTabsKey] = allTabIds.join(",");
		} else {
			delete hashObj[selector][allTabsKey];
		}

		const newHash = createBracketedKeyValueHash(hashObj);
		if (location.hash !== newHash) {
			const scrollX = window.scrollX;
			const scrollY = window.scrollY;
			history.replaceState(null, "", newHash);
			window.scrollTo(scrollX, scrollY);
		}
		lastKnownActiveTabId = activeTabId;
		lastKnownAllTabIds = allTabIds;
	}

	// Listen for tab changes (active tab)
	tabsEl.addEventListener("monster-tab-changed", (e) => {
		if (e.target !== tabsEl) return; // Ignore bubbled events
		const newActiveTabId =
			e.detail?.tab || e.detail?.name || e.detail?.reference;
		const currentTabs = tabsEl.getTabs().map(getTabKey);
		writeHash(newActiveTabId, currentTabs);
	});

	// Listen for tab additions
	const observer = new MutationObserver((mutationsList) => {
		let tabsChanged = false;
		for (const mutation of mutationsList) {
			if (
				mutation.type === "childList" &&
				(mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0)
			) {
				// Filter for actual tab elements
				const hasTabNodes =
					Array.from(mutation.addedNodes).some(
						(node) =>
							node.nodeType === 1 &&
							node.hasAttribute("data-monster-button-label"),
					) ||
					Array.from(mutation.removedNodes).some(
						(node) =>
							node.nodeType === 1 &&
							node.hasAttribute("data-monster-button-label"),
					);

				if (hasTabNodes) {
					tabsChanged = true;
					break;
				}
			}
		}
		if (tabsChanged) {
			const currentActiveTabId = tabsEl.getActiveTab();
			const currentTabs = tabsEl.getTabs().map(getTabKey);
			// Only update hash if the list of tabs has actually changed
			if (
				currentTabs.length !== lastKnownAllTabIds.length ||
				!currentTabs.every((id) => lastKnownAllTabIds.includes(id))
			) {
				writeHash(currentActiveTabId, currentTabs);
			}
		}
	});

	// Observe the tabsEl for direct child additions/removals
	observer.observe(tabsEl, { childList: true });

	// Initial write of all existing tabs to the hash
	const initialActiveTab = tabsEl.getActiveTab();
	const initialTabs = tabsEl.getTabs().map(getTabKey);
	writeHash(initialActiveTab, initialTabs);
}
