/**
 * ViewHistory — lightweight in-app navigation stack for rApps.
 *
 * Each rApp with hierarchical views instantiates one, calls push()
 * on forward navigation, and back() from the back button. Replaces
 * hardcoded data-back targets with a proper history stack.
 *
 * Browser history integration (opt-in):
 *   Pass `moduleId` to the constructor to sync push/back with the
 *   browser's history.pushState / popstate. TabCache's popstate handler
 *   dispatches `rspace-view-popstate` events for same-module navigation,
 *   which this class catches and calls `back()` automatically, then
 *   re-dispatches as `rspace-view-restored` for the component to re-render.
 */

export interface ViewEntry<V extends string> {
	view: V;
	context?: Record<string, unknown>;
}

const MAX_DEPTH = 20;

export class ViewHistory<V extends string> {
	private stack: ViewEntry<V>[] = [];
	private root: V;
	private moduleId: string | null;
	private _skipNextPush = false;

	/**
	 * @param rootView  Default view when stack is empty.
	 * @param moduleId  If provided, push/back sync with browser history.
	 */
	constructor(rootView: V, moduleId?: string) {
		this.root = rootView;
		this.moduleId = moduleId ?? null;

		if (this.moduleId) {
			window.addEventListener('rspace-view-popstate', this._onPopstate as EventListener);
		}
	}

	/** Record a forward navigation. Skips if top of stack is same view+context. */
	push(view: V, context?: Record<string, unknown>): void {
		const top = this.stack[this.stack.length - 1];
		if (top && top.view === view) return; // skip duplicate
		this.stack.push({ view, context });
		if (this.stack.length > MAX_DEPTH) this.stack.shift();

		// Sync with browser history
		if (this.moduleId && !this._skipNextPush) {
			const existing = history.state || {};
			history.pushState(
				{ ...existing, moduleId: existing.moduleId, spaceSlug: existing.spaceSlug, _view: view, _viewCtx: context },
				'',
			);
		}
		this._skipNextPush = false;
	}

	/** Pop and return the previous entry, or null if at root. */
	back(): ViewEntry<V> | null {
		if (this.stack.length <= 1) {
			this.stack = [];
			return { view: this.root };
		}
		this.stack.pop(); // remove current
		return this.stack[this.stack.length - 1] ?? { view: this.root };
	}

	/** True when there's history to go back to. */
	get canGoBack(): boolean {
		return this.stack.length > 1;
	}

	/** Peek at the previous entry without popping. */
	peekBack(): ViewEntry<V> | null {
		if (this.stack.length <= 1) return { view: this.root };
		return this.stack[this.stack.length - 2] ?? null;
	}

	/** Clear the stack and reset to a root view (e.g. on space switch). */
	reset(rootView?: V): void {
		if (rootView !== undefined) this.root = rootView;
		this.stack = [];
	}

	/** Clean up the popstate listener. Call when the component disconnects. */
	destroy(): void {
		if (this.moduleId) {
			window.removeEventListener('rspace-view-popstate', this._onPopstate as EventListener);
		}
	}

	/** Handle popstate events forwarded by TabCache for same-module nav. */
	private _onPopstate = (e: CustomEvent) => {
		if (e.detail?.moduleId !== this.moduleId) return;

		// Pop our stack without triggering another pushState
		const prev = this.back();
		if (!prev) return;

		// Tell the component to re-render with the restored view
		window.dispatchEvent(new CustomEvent('rspace-view-restored', {
			detail: { moduleId: this.moduleId, view: prev.view, context: prev.context },
		}));
	};
}
