import { instanceSymbol } from "../../constants.mjs";
import { ATTRIBUTE_ROLE } from "../../dom/constants.mjs";
import {
	assembleMethodSymbol,
	CustomElement,
	getSlottedElements,
	registerCustomElement,
} from "../../dom/customelement.mjs";
import {
	findTargetElementFromEvent,
	fireCustomEvent,
} from "../../dom/events.mjs";
import { isFunction } from "../../types/is.mjs";
import { WizardNavigationStyleSheet } from "./stylesheet/wizard-navigation.mjs";

const wizardNavigationElementSymbol = Symbol("wizardNavigationElement");
const wizardNavigationListElementSymbol = Symbol("wizardNavigationListElement");
const currentStepIndexSymbol = Symbol("currentStepIndex");
const stepsSymbol = Symbol("steps");
const isTransitioningSymbol = Symbol("isTransitioning");
const currentSubStepIndexSymbol = Symbol("currentSubStepIndex");

/**
 * A WizardNavigation component to display progress and allow navigation through a series of steps,
 * including support for nested sub-steps.
 *
 * @fragments /fragments/components/navigation/wizard-navigation
 * @example /examples/components/navigation/wizard-navigation-simple
 * @since 4.26.0
 * @copyright Volker Schukai
 * @summary A step-by-step wizard navigation for multi-stage forms and guided setup flows.
 * @fires monster-wizard-step-changed - Fired when the main active step changes.
 * @fires monster-wizard-substep-changed - Fired when the active sub-step changes.
 * @fires monster-wizard-completed - Fired when the entire wizard is marked as complete via completeAll().
 */
class WizardNavigation extends CustomElement {
	static get [instanceSymbol]() {
		return Symbol.for(
			"@schukai/monster/components/navigation/wizard-navigation@@instance",
		);
	}

	[assembleMethodSymbol]() {
		super[assembleMethodSymbol]();
		this[isTransitioningSymbol] = false;
		this[currentStepIndexSymbol] = -1;
		this[currentSubStepIndexSymbol] = -1;

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

		queueMicrotask(() => {
			importContent.call(this);
			this.activate(0, 0, { force: true });
		});

		return this;
	}

	/**
	 * 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} actions Action callbacks
	 * @property {function} actions.beforeStepChange - Callback fired before a main step change. Must return `true` or a Promise that resolves to `true` to allow the transition. Parameters are `fromIndex` and `toIndex`.
	 * @property {function} actions.click - General click handler callback.
	 */
	get defaults() {
		return Object.assign({}, super.defaults, {
			templates: {
				main: getTemplate(),
			},
			actions: {
				beforeStepChange: (fromIndex, toIndex) => true,
				click: () => {},
			},
		});
	}

	/**
	 * Navigates to the specified main step if the transition logic allows it.
	 * @param {number} index The index of the target step.
	 * @param {object} [options={}] Additional options.
	 * @param {boolean} [options.force=false] If true, the `beforeStepChange` callback is skipped.
	 * @returns {Promise<void>}
	 * @fires monster-wizard-step-changed
	 */
	async goToStep(index, options = {}) {
		if (typeof index !== "number") {
			console.error("WizardNavigation.goToStep: index must be a number.");
			return;
		}

		const { force = false } = options;
		const stepsData = this[stepsSymbol];
		const oldIndex = this[currentStepIndexSymbol];

		if (
			!stepsData ||
			index < 0 ||
			index >= stepsData.length ||
			index === oldIndex ||
			this[isTransitioningSymbol]
		) {
			return;
		}

		this[isTransitioningSymbol] = true;

		if (!force) {
			const callback = this.getOption("actions.beforeStepChange");
			if (isFunction(callback)) {
				const isAllowed = await Promise.resolve(
					callback.call(this, oldIndex, index),
				);
				if (!isAllowed) {
					this[isTransitioningSymbol] = false;
					return;
				}
			}
		}

		stepsData.forEach((stepData, i) => {
			stepData.element.classList.remove("step-active");
			stepData.element.setAttribute("aria-selected", "false");

			if (stepData.subStepList) {
				stepData.subStepList.style.maxHeight =
					i === index ? stepData.subStepList.scrollHeight + "px" : "0px";
			}

			if (i < index) {
				stepData.element.classList.add("step-completed");
				stepData.subSteps.forEach((sub) =>
					sub.classList.add("sub-item-completed"),
				);
			} else {
				stepData.element.classList.remove("step-completed");
				stepData.subSteps.forEach((sub) =>
					sub.classList.remove("sub-item-completed"),
				);
			}
		});

		if (oldIndex !== -1 && stepsData[oldIndex]) {
			stepsData[oldIndex].subSteps.forEach((sub) =>
				sub.classList.remove("sub-item-active"),
			);
		}

		this[currentSubStepIndexSymbol] = -1;
		this[currentStepIndexSymbol] = index;

		const currentStepData = stepsData[index];
		currentStepData.element.classList.remove("step-completed");
		currentStepData.element.classList.add("step-active");
		currentStepData.element.setAttribute("aria-selected", "true");

		fireCustomEvent(this, "monster-wizard-step-changed", {
			newIndex: index,
			oldIndex: oldIndex,
			element: currentStepData.element,
		});

		this[isTransitioningSymbol] = false;
	}

	/**
	 * Activates a specific main step and sub-step.
	 * @param {number} mainIndex The index of the main step.
	 * @param {number} subIndex The index of the sub-step.
	 * @param {object} [options={}] Additional options.
	 * @param {boolean} [options.force=false] If true, the `beforeStepChange` callback is skipped.
	 * @returns {Promise<void>}
	 * @fires monster-wizard-substep-changed
	 */
	async activate(mainIndex, subIndex, options = {}) {
		if (typeof mainIndex !== "number" || typeof subIndex !== "number") {
			console.error(
				"WizardNavigation.activate: mainIndex and subIndex must be numbers.",
			);
			return;
		}

		await this.goToStep(mainIndex, options);

		const stepsData = this[stepsSymbol];
		if (!stepsData || !stepsData[mainIndex]) return;

		const targetStep = stepsData[mainIndex];
		if (
			!targetStep.subSteps ||
			subIndex < 0 ||
			subIndex >= targetStep.subSteps.length
		) {
			return;
		}

		stepsData.forEach((step) => {
			step.subSteps.forEach((subStep) =>
				subStep.classList.remove("sub-item-active"),
			);
		});

		for (let i = 0; i < subIndex; i++) {
			if (targetStep.subSteps[i]) {
				targetStep.subSteps[i].classList.add("sub-item-completed");
			}
		}

		const targetSubStep = targetStep.subSteps[subIndex];
		targetSubStep.classList.remove("sub-item-completed");
		targetSubStep.classList.add("sub-item-active");
		this[currentSubStepIndexSymbol] = subIndex;

		fireCustomEvent(this, "monster-wizard-substep-changed", {
			mainIndex,
			subIndex,
			element: targetSubStep,
		});
	}

	/**
	 * Navigates to the next step or sub-step in sequence.
	 * @returns {Promise<void>}
	 */
	async next() {
		const mainIndex = this[currentStepIndexSymbol];
		const subIndex = this[currentSubStepIndexSymbol];
		const stepsData = this[stepsSymbol];

		if (mainIndex === -1 || !stepsData || !stepsData[mainIndex]) return;

		const currentStepData = stepsData[mainIndex];
		const hasSubSteps = currentStepData.subSteps.length > 0;

		if (hasSubSteps && subIndex < currentStepData.subSteps.length - 1) {
			await this.activate(mainIndex, subIndex + 1);
			return;
		}

		const nextMainIndex = mainIndex + 1;
		if (nextMainIndex < stepsData.length) {
			if (stepsData[nextMainIndex].subSteps.length > 0) {
				await this.activate(nextMainIndex, 0);
			} else {
				await this.goToStep(nextMainIndex);
			}
		}
	}

	/**
	 * Navigates to the previous step or sub-step in sequence.
	 * @returns {Promise<void>}
	 */
	async previous() {
		const mainIndex = this[currentStepIndexSymbol];
		const subIndex = this[currentSubStepIndexSymbol];
		const stepsData = this[stepsSymbol];

		if (subIndex > 0) {
			await this.activate(mainIndex, subIndex - 1);
			return;
		}

		const prevMainIndex = mainIndex - 1;
		if (prevMainIndex >= 0) {
			const prevStepData = stepsData[prevMainIndex];
			if (prevStepData.subSteps.length > 0) {
				await this.activate(prevMainIndex, prevStepData.subSteps.length - 1);
			} else {
				await this.goToStep(prevMainIndex);
			}
		}
	}

	/**
	 * Marks the current step as completed and automatically navigates to the next step.
	 * @returns {Promise<void>}
	 */
	async completeAndNext() {
		const currentIndex = this[currentStepIndexSymbol];
		const stepsData = this[stepsSymbol];

		if (currentIndex === -1 || !stepsData || !stepsData[currentIndex]) {
			return;
		}

		const currentStepData = stepsData[currentIndex];
		currentStepData.element.classList.add("step-completed");
		currentStepData.element.classList.remove("step-active");

		currentStepData.subSteps.forEach((sub) => {
			sub.classList.add("sub-item-completed");
			sub.classList.remove("sub-item-active");
		});

		await this.next();
	}

	/**
	 * Marks the entire wizard workflow as completed.
	 * @fires monster-wizard-completed
	 */
	completeAll() {
		const stepsData = this[stepsSymbol];
		if (!stepsData) return;

		stepsData.forEach((stepData) => {
			stepData.element.classList.add("step-completed");
			stepData.element.classList.remove("step-active");
			stepData.element.setAttribute("aria-selected", "false");

			stepData.subSteps.forEach((sub) => {
				sub.classList.add("sub-item-completed");
				sub.classList.remove("sub-item-active");
			});

			if (stepData.subStepList) {
				stepData.subStepList.style.maxHeight = "0px";
			}
		});

		this[currentStepIndexSymbol] = -1;
		this[currentSubStepIndexSymbol] = -1;

		fireCustomEvent(this, "monster-wizard-completed", {
			detail: { message: "All steps completed." },
		});
	}

	/**
	 * Gets the index of the currently active main step.
	 * @returns {number} The index of the current step, or -1 if none is active.
	 */
	getCurrentStepIndex() {
		return this[currentStepIndexSymbol];
	}

	/**
	 * Gets the index of the currently active sub-step within the main step.
	 * @returns {number} The index of the current sub-step, or -1 if none is active.
	 */
	getCurrentSubStepIndex() {
		return this[currentSubStepIndexSymbol];
	}

	static getTag() {
		return "monster-wizard-navigation";
	}

	static getCSSStyleSheet() {
		return [WizardNavigationStyleSheet];
	}
}

/** @private */
function initEventHandler() {
	const self = this;
	this[wizardNavigationElementSymbol].addEventListener(
		"click",
		function (event) {
			const targetStepElement = findTargetElementFromEvent(event, "li.step");
			if (!targetStepElement) return;

			const targetIndex = self[stepsSymbol].findIndex(
				(stepData) => stepData.element === targetStepElement,
			);

			if (targetIndex !== -1) {
				self.goToStep(targetIndex);
			}
		},
	);
}

/** @private */
function initControlReferences() {
	this[wizardNavigationElementSymbol] = this.shadowRoot.querySelector(
		`[${ATTRIBUTE_ROLE}="control"]`,
	);
	this[wizardNavigationListElementSymbol] = this.shadowRoot.querySelector(
		`[${ATTRIBUTE_ROLE}="list"]`,
	);
}

/** @private */
function importContent() {
	const elements = getSlottedElements.call(this, "ol.wizard-steps");
	elements.forEach((element) => {
		const clonedContent = element.cloneNode(true);
		this[wizardNavigationListElementSymbol].innerHTML = "";
		this[wizardNavigationListElementSymbol].appendChild(clonedContent);

		const stepElements =
			this[wizardNavigationListElementSymbol].querySelectorAll("li.step");

		this[stepsSymbol] = Array.from(stepElements).map((stepEl) => {
			const subStepList = stepEl.querySelector("ul");
			const subSteps = subStepList
				? Array.from(subStepList.querySelectorAll("li"))
				: [];

			if (subStepList) {
				subStepList.style.maxHeight = "0px";
				subStepList.style.overflow = "hidden";
				subStepList.style.transition = "max-height 0.4s ease-in-out";
			}

			return {
				element: stepEl,
				subStepList: subStepList,
				subSteps: subSteps,
			};
		});

		this[currentStepIndexSymbol] = -1;
		this[currentSubStepIndexSymbol] = -1;

		this[wizardNavigationListElementSymbol]
			.querySelector("ol")
			?.setAttribute("role", "tablist");

		this[stepsSymbol].forEach((stepData) => {
			stepData.element.setAttribute("role", "tab");
			stepData.element.setAttribute("aria-selected", "false");
		});
	});
}

/** @private */
function getTemplate() {
	return `
		<div data-monster-role="control" part="control">
			<slot style="display: none;"></slot>
			<div data-monster-role="list"></div>
		</div>`;
}

registerCustomElement(WizardNavigation);
